简体   繁体   English

如何将 WPF 模型映射到实体框架模型

[英]How to Map WPF Model To Entity Framework Model

I have troubles mapping database entities to WPF models and back.我在将数据库实体映射到 WPF 模型并返回时遇到了麻烦。 My application works with WPF model, that implements INotifyPropertyChanged interface.我的应用程序使用 WPF 模型,它实现了 INotifyPropertyChanged 接口。 Everytime I need to store it in database, I need to map my WPF model to database model.每次我需要将它存储在数据库中时,我都需要将我的 WPF 模型映射到数据库模型。 I've created a complicated mapper to do so, but it end up having cycle dependancy.我为此创建了一个复杂的映射器,但它最终具有循环依赖性。 How i can efficently map my WPF model to database model?我如何有效地将我的 WPF 模型映射到数据库模型? My database model:我的数据库模型:

public class DbUser
{
    [Key]
    public Guid UserId { get; set; }
    public List<DbSeries> UserSeries { get; set; }
}

public class DbSeries
{
    [Key]
    public Guid SeriesId { get; set; }

    public List<DbDropPhoto> DropPhotosSeries { get; set; }
    public virtual DbReferencePhoto ReferencePhotoForSeries { get; set; }

    public Guid CurrentUserId { get; set; }
    public DbUser CurrentUser { get; set; }
}

public class DbReferencePhoto
{
    [Key]
    public Guid ReferencePhotoId { get; set; }

    public virtual DbSimpleLine SimpleLine { get; set; }

    public virtual DbSeries Series { get; set; }
}

public class DbDropPhoto
{
    [Key]
    public Guid DropPhotoId { get; set; }

    public virtual DbSimpleLine SimpleHorizontalLine { get; set; }
    public virtual DbSimpleLine SimpleVerticalLine { get; set; }

    public virtual DbDrop Drop { get; set; }

    public Guid CurrentSeriesId { get; set; }
    public DbSeries CurrentSeries { get; set; }
}

public class DbDrop
{
    [Key]
    public Guid DropId { get; set; }

    public virtual DbDropPhoto DropPhoto { get; set; }
}

public class DbSimpleLine
{
    [Key]
    public Guid SimpleLineId { get; set; }

    public virtual DbReferencePhoto ReferencePhoto { get; set; }
    public virtual DbDropPhoto DropPhotoHorizontalLine { get; set; }
    public virtual DbDropPhoto DropPhotoVerticalLine { get; set; }
}

fluent api configuration goes like this: fluent api 配置是这样的:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<DbUser>()
            .HasMany(s => s.UserSeries)
            .WithRequired(g => g.CurrentUser)
            .HasForeignKey(s => s.CurrentUserId);

        modelBuilder.Entity<DbSeries>()
            .HasMany(s => s.DropPhotosSeries)
            .WithRequired(g => g.CurrentSeries)
            .HasForeignKey(s => s.CurrentSeriesId)
            .WillCascadeOnDelete();

        modelBuilder.Entity<DbSeries>()
            .HasRequired(s => s.ReferencePhotoForSeries)
            .WithRequiredPrincipal(ad => ad.Series);

        modelBuilder.Entity<DbDropPhoto>()
            .HasRequired(s => s.Drop)
            .WithRequiredPrincipal(ad => ad.DropPhoto);

        modelBuilder.Entity<DbDropPhoto>()
            .HasRequired(s => s.SimpleHorizontalLine)
            .WithRequiredPrincipal(ad => ad.DropPhotoHorizontalLine);

        modelBuilder.Entity<DbDropPhoto>()
            .HasRequired(s => s.SimpleVerticalLine)
            .WithRequiredPrincipal(ad => ad.DropPhotoVerticalLine);

        modelBuilder.Entity<DbReferencePhoto>()
            .HasRequired(s => s.SimpleLine)
            .WithRequiredPrincipal(ad => ad.ReferencePhoto);
    }

my WPF model:我的 WPF 模型:

public class User : INotifyPropertyChanged
{
    public User()
    {
        _userSeries = new ObservableCollection<Series>();
        _userSeries.CollectionChanged += _userSeries_CollectionChanged;
    }

    private void _userSeries_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsAnySelectedSeriesCanDrawPlot)));
    }

    public Guid UserId { get; set; }

    private ObservableCollection<Series> _userSeries;
    public ObservableCollection<Series> UserSeries
    {
        get
        {
            return _userSeries;
        }
        set
        {
            _userSeries = value;
            OnPropertyChanged(new PropertyChangedEventArgs("UserSeries"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChanged?.Invoke(this, e);
    }
}

public class Series : INotifyPropertyChanged
{
    private Guid _currentUserId;
    public Guid CurrentUserId
    {
        get
        {
            return _currentUserId;
        }
        set
        {
            _currentUserId = value;
            OnPropertyChanged(new PropertyChangedEventArgs("CurrentUserId"));
        }
    }

    private User _currentUser;
    public User CurrentUser
    {
        get
        {
            return _currentUser;
        }
        set
        {
            _currentUser = value;
            OnPropertyChanged(new PropertyChangedEventArgs("CurrentUser"));
        }
    }

    public Series()
    {
        _dropPhotosSeries = new ObservableCollection<DropPhoto>();
        _dropPhotosSeries.CollectionChanged += _dropPhotosSeries_CollectionChanged;
    }

    private void _dropPhotosSeries_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(nameof(CanDrawPlot)));
        CurrentUser.OnPropertyChanged(new PropertyChangedEventArgs(nameof(User.IsAnySelectedSeriesCanDrawPlot)));
    }

    public Guid SeriesId { get; set; }

    private ObservableCollection<DropPhoto> _dropPhotosSeries;
    public ObservableCollection<DropPhoto> DropPhotosSeries
    {
        get
        {
            return _dropPhotosSeries;
        }
        set
        {
            _dropPhotosSeries = value;
            OnPropertyChanged(new PropertyChangedEventArgs("DropPhotosSeries"));
        }
    }

    private ReferencePhoto _referencePhotoForSeries;
    public ReferencePhoto ReferencePhotoForSeries
    {
        get
        {
            return _referencePhotoForSeries;
        }
        set
        {
            _referencePhotoForSeries = value;
            OnPropertyChanged(new PropertyChangedEventArgs("ReferencePhotoForSeries"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(IntervalBetweenPhotos))
            OnPropertyChanged(new PropertyChangedEventArgs(nameof(CanDrawPlot)));

        if (e.PropertyName == nameof(IsChecked))
            CurrentUser.OnPropertyChanged(new PropertyChangedEventArgs(nameof(User.IsAnySelectedSeriesCanDrawPlot))); ;

        PropertyChanged?.Invoke(this, e);
    }
}

public class ReferencePhoto : INotifyPropertyChanged
{
    private Series _series;
    public Series Series
    {
        get
        {
            return _series;
        }
        set
        {
            _series = value;
            OnPropertyChanged(new PropertyChangedEventArgs("Series"));
        }
    }

    public Guid ReferencePhotoId { get; set; }

    private SimpleLine _simpleLine;
    public SimpleLine SimpleLine
    {
        get
        {
            return _simpleLine;
        }
        set
        {
            _simpleLine = value;
            OnPropertyChanged(new PropertyChangedEventArgs("SimpleLine"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChanged?.Invoke(this, e);
    }
}

public class DropPhoto : INotifyPropertyChanged
{
    private Guid _currentSeriesId;
    public Guid CurrentSeriesId
    {
        get
        {
            return _currentSeriesId;
        }
        set
        {
            _currentSeriesId = value;
            OnPropertyChanged(new PropertyChangedEventArgs("CurrentSeriesId"));
        }
    }

    private Series _currentSeries;
    public Series CurrentSeries
    {
        get
        {
            return _currentSeries;
        }
        set
        {
            _currentSeries = value;
            OnPropertyChanged(new PropertyChangedEventArgs("CurrentSeries"));
        }
    }

    public Guid DropPhotoId { get; set; }

    private SimpleLine _simpleHorizontalLine;
    public SimpleLine SimpleHorizontalLine
    {
        get
        {
            return _simpleHorizontalLine;
        }
        set
        {
            _simpleHorizontalLine = value;
            OnPropertyChanged(new PropertyChangedEventArgs("SimpleHorizontalLine"));
        }
    }

    private SimpleLine _simpleVerticalLine;
    public SimpleLine SimpleVerticalLine
    {
        get
        {
            return _simpleVerticalLine;
        }
        set
        {
            _simpleVerticalLine = value;
            OnPropertyChanged(new PropertyChangedEventArgs("SimpleVerticalLine"));
        }
    }

    private Drop _drop;
    public Drop Drop
    {
        get
        {
            return _drop;
        }
        set
        {
            _drop = value;
            OnPropertyChanged(new PropertyChangedEventArgs("Drop"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChanged?.Invoke(this, e);
    }
}

public class SimpleLine
{
    public Guid SimpleLineId { get; set; }

    public ReferencePhoto ReferencePhoto { get; set; }
    public DropPhoto DropPhotoHorizontalLine { get; set; }
    public DropPhoto DropPhotoVerticalLine { get; set; }
}

For example i need to create a new Series.例如,我需要创建一个新系列。 My application implements repository pattern.我的应用程序实现了存储库模式。 Series creation method goes like this:系列创建方法如下:

    public async Task CreateSeries(DbSeries series)
    {
        using (var context = new DDropContext())
        {
            var createdSeries = context.Series.Add(series);

            await context.SaveChangesAsync();
        }
    }

At first I create new Series:首先我创建新系列:

            Series seriesToAdd = new Series()
            {
                SeriesId = Guid.NewGuid(),
                Title = seriesTitle,                    
                AddedDate = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"),
                ReferencePhotoForSeries = new ReferencePhoto()
                {
                    ReferencePhotoId = Guid.NewGuid(),
                    Series = CurrentSeries,
                    SimpleLine = new SimpleLine { SimpleLineId = Guid.NewGuid()}
                },
                CurrentUser = User,
                CurrentUserId = User.UserId
            };

Then i need to map it to DbSeries and create new DbSeries:然后我需要将它映射到 DbSeries 并创建新的 DbSeries:

            try
            {

                await _dDropRepository.CreateSeries(DDropDbEntitiesMapper.SingleSeriesToSingleDbSeries(seriesToAdd, User));

            }
            catch (Exception)
            {

            }   

To map Series to DbSeries i use very complicated mapper, which doesn't work properly:要将系列映射到 DbSeries,我使用了非常复杂的映射器,但它无法正常工作:

    public static DbSeries SingleSeriesToSingleDbSeries(Series userSeries, User user)
    {
        DbSeries singleSeries = new DbSeries();
        List<DbDropPhoto> dropPhotosSeries = new List<DbDropPhoto>();

        foreach (var dropPhoto in userSeries.DropPhotosSeries)
        {
            DbDropPhoto newDbDropPhoto = new DbDropPhoto()
            {
                Name = dropPhoto.Name,
                Content = dropPhoto.Content,
                AddedDate = dropPhoto.AddedDate,
                DropPhotoId = dropPhoto.DropPhotoId,
                XDiameterInPixels = dropPhoto.XDiameterInPixels,
                YDiameterInPixels = dropPhoto.YDiameterInPixels,
                ZDiameterInPixels = dropPhoto.ZDiameterInPixels,
                CurrentSeries = SingleSeriesToSingleDbSeries(userSeries, user),
                CurrentSeriesId = userSeries.SeriesId,

            };

            DbSimpleLine newHorizontalDbSimpleLine = new DbSimpleLine
            {
                X1 = dropPhoto.SimpleHorizontalLine.X1,
                X2 = dropPhoto.SimpleHorizontalLine.X2,
                Y1 = dropPhoto.SimpleHorizontalLine.Y1,
                Y2 = dropPhoto.SimpleHorizontalLine.Y2,
                DropPhotoHorizontalLine = newDbDropPhoto,
                SimpleLineId = dropPhoto.SimpleHorizontalLine.SimpleLineId,
            };

            DbSimpleLine newVerticalDbSimpleLine = new DbSimpleLine
            {
                X1 = dropPhoto.SimpleVerticalLine.X1,
                X2 = dropPhoto.SimpleVerticalLine.X2,
                Y1 = dropPhoto.SimpleVerticalLine.Y1,
                Y2 = dropPhoto.SimpleVerticalLine.Y2,
                DropPhotoVerticalLine = newDbDropPhoto,
                SimpleLineId = dropPhoto.SimpleHorizontalLine.SimpleLineId,
            };

            DbDrop newDbDrop = new DbDrop()
            {
                DropId = dropPhoto.Drop.DropId,
                RadiusInMeters = dropPhoto.Drop.RadiusInMeters,
                VolumeInCubicalMeters = dropPhoto.Drop.VolumeInCubicalMeters,
                XDiameterInMeters = dropPhoto.Drop.XDiameterInMeters,
                YDiameterInMeters = dropPhoto.Drop.YDiameterInMeters,
                ZDiameterInMeters = dropPhoto.Drop.ZDiameterInMeters,
                DropPhoto = newDbDropPhoto,
            };

            newDbDropPhoto.Drop = newDbDrop;
            newDbDropPhoto.SimpleHorizontalLine = newHorizontalDbSimpleLine;
            newDbDropPhoto.SimpleVerticalLine = newVerticalDbSimpleLine;

            dropPhotosSeries.Add(newDbDropPhoto);
        }

        if (userSeries.ReferencePhotoForSeries != null)
        {
            var referencePhoto = new DbReferencePhoto
            {
                Content = userSeries.ReferencePhotoForSeries.Content,
                Name = userSeries.ReferencePhotoForSeries.Name,
                PixelsInMillimeter = userSeries.ReferencePhotoForSeries.PixelsInMillimeter,
                ReferencePhotoId = userSeries.ReferencePhotoForSeries.ReferencePhotoId,
                Series = singleSeries,
            };

            var simpleLineForReferencePhoto = new DbSimpleLine
            {
                X1 = userSeries.ReferencePhotoForSeries.SimpleLine.X1,
                X2 = userSeries.ReferencePhotoForSeries.SimpleLine.X2,
                Y1 = userSeries.ReferencePhotoForSeries.SimpleLine.Y1,
                Y2 = userSeries.ReferencePhotoForSeries.SimpleLine.Y2,
                ReferencePhoto = referencePhoto,
                SimpleLineId = userSeries.ReferencePhotoForSeries.SimpleLine.SimpleLineId,
            };

            referencePhoto.SimpleLine = simpleLineForReferencePhoto;

            singleSeries.ReferencePhotoForSeries = referencePhoto;
        }

        singleSeries.DropPhotosSeries = dropPhotosSeries;
        singleSeries.IntervalBetweenPhotos = userSeries.IntervalBetweenPhotos;
        singleSeries.AddedDate = userSeries.AddedDate;
        singleSeries.SeriesId = userSeries.SeriesId;
        singleSeries.Title = userSeries.Title;
        singleSeries.CurrentUser = UserToDbUser(user);
        singleSeries.CurrentUserId = user.UserId;

        return singleSeries;
    }

The main proplem is that DbSeries has in it public DbUser CurrentUser, so when i map Series to DbSeries i need to fill it with User, that i convert to DbUser, which results stackoverflow exception (method SingleSeriesToSingleDbSeries calls itself) Is there any better way to achieve my goal?主要问题是 DbSeries 中有公共 DbUser CurrentUser,所以当我将 Series 映射到 DbSeries 时,我需要用 User 填充它,我将其转换为 DbUser,这导致 stackoverflow 异常(方法 SingleSeriesToSingleDbSeries 调用自身)有没有更好的方法达到我的目标?

From what I can see your approach feels a little bit backwards.从我所看到的,你的方法感觉有点倒退。 Typically I will start with the domain models (Entities, mapped to DB tables) then from that I will define the view models for the view.通常,我将从域模型(实体,映射到数据库表)开始,然后我将为视图定义视图模型。 The important thing here is that the view models do not generally map 1-to-1 to the domain models.这里重要的是视图模型通常不会一对一地映射到域模型。 View models serve the needs of the view, so while I can take a 1-to-1 representation of an entity (set up with NotifyPropertyChanged etc.) and bind the controls and such to that hierarchy of entities, it's better if I simply define only the fields the view needs (irregardless of where in the entity model they come from) and let the mapper transpose those values from the entities to the view model.视图模型满足视图的需求,因此虽然我可以采用实体的 1 对 1 表示(使用 NotifyPropertyChanged 等设置)并将控件等绑定到该实体层次结构,但最好是我简单地定义只有视图需要的字段(无论它们来自实体模型中的哪个位置),并让映射器将这些值从实体转置到视图模型。 This reduces the payload sent from server to client (and back) and keeps from exposing more about your domain than your view needs to know about.这减少了从服务器发送到客户端(和返回)的有效负载,并避免暴露更多关于您的域的信息,而不是您的视图需要知道的。 (Visible in debugging tools) (在调试工具中可见)

Automapper is definitely the tool of choice for doing this largely due to it's ProjectTo method which can work with EF's IQueryable to compose queries to pull back only the fields from the entity graph that the view model, and any related view models need. Automapper 绝对是执行此操作的首选工具,这主要是因为它的ProjectTo方法可以与 EF 的IQueryable来组合查询,以仅从视图模型和任何相关视图模型需要的实体图中拉回字段。 In some cases you will need to set up some configuration for non-conventional mappings, but the call to perform the mapping is a 1-liner.在某些情况下,您需要为非常规映射设置一些配置,但执行映射的调用是 1-liner。

When it comes to editing entities, these will be more of a 1-to-1 representation between the entity and the view model, but for the most part I treat editing at a single level basis.当涉及到编辑实体时,实体和视图模型之间将更多地是一对一的表示,但在大多数情况下,我在单一级别的基础上处理编辑。 For instance if I have an Order with associated order lines, products, customer, etc. then a create order view model will likely contain a whole hierarchy of related details (order lines with product ID, quantity, etc.) An Edit Order action would not necessarily.例如,如果我有一个带有关联订单行、产品、客户等的订单,那么创建订单视图模型可能会包含相关详细信息的整个层次结构(带有产品 ID、数量等的订单行)。 Edit Order 操作将不必要。 Depending on what actions a user can perform, the view model would just contain the relevant fields that can be updated as opposed to trying to pass back and forth a representation of the complete order.根据用户可以执行的操作,视图模型将只包含可以更新的相关字段,而不是尝试来回传递完整订单的表示。 The issue with passing a complete order back & forth is transmission packet size considerations, as well as the opportunity for data that should not be updated to be updated due to tampering.来回传递完整命令的问题是传输数据包大小的考虑,以及由于篡改而更新不应该更新的数据的机会。 (Why passing entities back & forth and using Attach / Update should be heavily discouraged.) Automapper can assist with mapping data back to entities, but I tend to hand-write the update mappings myself since I'm going to be validating and inspecting the values anyways. (为什么应该强烈反对来回传递实体和使用Attach / Update 。)Automapper 可以帮助将数据映射回实体,但我倾向于自己手写更新映射,因为我将要验证和检查反正价值观。 I don't often use Automapper for mapping back to Entities because I don't want to trust things like User IDs/Customer IDs or such coming back from a client.我不经常使用 Automapper 映射回实体,因为我不想相信诸如用户 ID/客户 ID 之类的东西或从客户端返回的内容。 These should be loaded based on session state.这些应该根据会话状态加载。 Though provided you have validation in place reliably then Automapper can easily help out with Insert type mapping back to Entities.尽管提供了可靠的验证,但 Automapper 可以轻松帮助插入类型映射回实体。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM