使用自动映射器简洁地插入/更新/删除可枚举的子实体 [英] Concisely Insert/Update/Delete Enumerable Child Entities using Automapper

查看:91
本文介绍了使用自动映射器简洁地插入/更新/删除可枚举的子实体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用ASP.Net MVC应用程序,并且我有一个"Report"对象,该对象具有相关的枚举,例如日程表和注释.使用AutoMapper,可以很容易地将报表实体转换为视图模型,然后再转换回来,但是当我尝试将报表对象(从视图模型映射到现有实体)保存回数据库时,却遇到了问题.

I am working on an ASP.Net MVC application and I have a "Report" object that has related enumerables such as schedules and comments. Using AutoMapper, it has been easy to convert from a report entity to a View Model and back, but I have issues when I try to save the Report object (mapped to an existing entity from a view model) back to the database.

更具体地说,我似乎无法使用automapper简洁地更新现有实体,插入新实体以及删除旧实体.例如,每当我将进度表从视图模型映射到报表实体时,它都会删除现有的进度表,然后创建新的进度表(具有递增的索引).这是我的代码:

More specifically, I can't seem to concisely update existing entities, insert new entities, and delete old entities using automapper. For instance whenever I map schedules from the view model to a report entity, it deletes the existing schedules and then creates new ones (with incremented indexes). This is my code:

public class ScheduleViewModel
{
    public int ScheduleID { get; set; }
    public int ReportID { get; set; }
    public int Month { get; set; }
    public int Day { get; set; }
    public string Description { get; set; }
}

public class ReportViewModel
{
    public int ReportID { get; set; }
    public List<ScheduleViewModel> Schedules { get; set; }

    public void Save()
    {
        dbContext db = new dbContext();
        Report original = db.Reports.SingleOrDefault(o => o.ReportID == ReportID);

        var config = new MapperConfiguration(cfg =>
        {
            cfg.CreateMap<ReportViewModel, Report>();
        });

        config.AssertConfigurationIsValid();
        var mapper = config.CreateMapper();
        mapper.Map(this, original);
        db.SaveChanges();
    }
}

我的Report对象具有一个关系键(和一个"Schedules"导航属性),因此所有内容都已成功地从我的视图模型映射到原始" Report.由于尚未分配新的日程表,因此ScheduleID为0,因为它们是我想要的,并且使用自动增量将它们添加到数据库中.现有的计划在映射到原始"报表对象时会保留其ScheduleID,但是一旦调用SaveChanges,便会接收到递增的ID.

My Report object has a relational key (and a "Schedules" navigational property), so everything is mapped successfully from my view model to the "original" Report. New schedules have a ScheduleID of 0, since they haven't been assigned, and they get added to the database using the auto-increment, which is what I want. The existing schedules maintain their ScheduleID when mapped to the "original" report object, but then recieve incremented IDs once SaveChanges is called.

据我了解,无论视图模型ID属性是否与数据库中的主键匹配(在这种情况下,它是ReportID和ScheduleID的组合),我都会在上下文中附加新的计划.是否有一种干净的方法,使用某种ForMember(report => report.Schedules)表达式,如果View Model对象可以映射到现有的Key,则该表达式可以使Entity Framework理解为不破坏我现有的实体?

As I understand it, I'm attaching new schedules to the context whether or not the view model ID properties match the primary key in the database (in this case it is a composite of ReportID and ScheduleID). Is there a clean way, using some sort of ForMember(report => report.Schedules), expression that makes Entity Framework understand to not destroy my existing entities if a View Model object can map to an existing Key?

我正在寻找功能类似于下面代码的内容,但是由于我的报表对象将附加许多可枚举的属性,因此我不想为每个对象维护以下部分:

I am looking for something that functions similar to the code below, but since I will have many enumerable properties attached to my report objects, I don't want to maintain these sections for each:

foreach (Schedule schedule in db.Schedules.Where(s => s.ReportID == this.ReportID))
{
    ScheduleViewModel svm = this.Schedules.FirstOrDefault(s => s.ScheduleID == schedule.ScheduleID);

    //Update Existing
    if (svm != null)
        db.Entry(schedule).CurrentValues.SetValues(svm);

    //Delete Missing
    else
        db.Entry(schedule).State = System.Data.Entity.EntityState.Deleted;
}

//Insert New
foreach(ScheduleViewModel svm in this.Schedules.Where(s => s.ScheduleID == 0))
{
    svm.ReportID = ReportID;
    Schedule schedule = new Schedule() {};
    db.Schedules.Add(schedule);
    db.Entry(schedule).CurrentValues.SetValues(svm);
}

推荐答案

显然,AutoMapper当前无法实现.AutoMapper不会映射单个实体,而是会破坏现有的实体集合并创建一个具有相同属性的新实体.我确信对于某些应用程序来说它可以很好地工作,但是通过Entity Framework的更改跟踪,它告诉数据库删除现有记录并插入具有新ID的新记录.不幸的是,似乎必须使用我在原始问题中发布的方法来单独/手动映射馆藏.不过,我没有对每个集合重复此操作,而是编写了一个通用处理程序,该处理程序将使用指定的键将模型集合映射到实体集合-而不破坏实体:

Apparently this is not currently possible with AutoMapper. Instead of mapping individual entities, AutoMapper destroys the existing entity collection and creates a new one with the same properties. I'm sure that works fine for some applications, but with Entity Framework's change tracking it is telling the database to delete the existing records and insert new ones, with new IDs. Unfortunately it seems collections have to be mapped individually/manually using the method I posted in my original question. Rather than repeating that for every collection, though, I wrote a generic handler that will map a model collection to an entity collection using a specified key -- without destroying the entities:

public void UpdateEntitySet<T>(IEnumerable<object> models, IEnumerable<T> entities, string key) where T : class
{
    Dictionary<object, T> entityDictionary = new Dictionary<object, T>();

    foreach(T entity in entities)
    {
        var entityKey = entity.GetType().GetProperty(key).GetValue(entity);
        entityDictionary.Add(entityKey, entity);
    }

    if (models != null)
    {
        foreach (object model in models)
        {
            var modelKey = model.GetType().GetProperty(key).GetValue(model);
            var existingEntity = entityDictionary.SingleOrDefault(d => Object.Equals(d.Key, modelKey)).Value;

            if (existingEntity == null)
            {
                var newEntity = db.Set<T>().Create();
                db.Set<T>().Add(newEntity);
                db.Entry(newEntity).CurrentValues.SetValues(model);
            }
            else
            {
                db.Entry(existingEntity).CurrentValues.SetValues(model);
                entityDictionary.Remove(entityDictionary.Single(d => Object.Equals(d.Key, modelKey)).Key);
            }
        }
    }

    for (int i = 0; i < entityDictionary.Count; i++)
        db.Entry(entityDictionary.ElementAt(i).Value).State = System.Data.Entity.EntityState.Deleted;
}

如果您有复合键,则必须稍微修改一下代码,否则可以通过AutoMapper忽略映射,然后使用上述方法,如下所示:

If you have a composite key, you'll have to modify the code a little, otherwise you can ignore the mapping with AutoMapper and then use the method above like so:

IEnumerable<Schedule> ScheduleEntities = db.Set<Schedule>().Where(s => s.ReportID == ReportID);
UpdateEntitySet<Schedule>(ScheduleViewModels, ScheduleEntities, "ScheduleID");

这篇关于使用自动映射器简洁地插入/更新/删除可枚举的子实体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆