使用Automapper简明地插入/更新/删除可枚举的子实体

时间:2018-05-22 03:39:48

标签: c# entity-framework automapper

我正在开发一个ASP.Net MVC应用程序,我有一个“报告”对象,它有相关的枚举,如日程安排和评论。使用AutoMapper,很容易从报表实体转换为View Model并返回,但是当我尝试将Report对象(从视图模型映射到现有实体)保存回数据库时,我遇到了问题。

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

Bot2

我的报告对象有一个关系密钥(以及“计划”导航属性),因此所有内容都从我的视图模型成功映射到“原始”报告。新计划的ScheduleID为0,因为它们尚未分配,并且使用自动增量将它们添加到数据库中,这就是我想要的。现有的计划在映射到“原始”报告对象时保持其ScheduleID,但是一旦调用了SaveChanges,它就会收到递增的ID。

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

我正在寻找与下面的代码类似的功能,但由于我的报表对象附加了许多可枚举的属性,我不想为每个部分维护这些部分:

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();
    }
}

1 个答案:

答案 0 :(得分:-1)

显然,AutoMapper目前无法实现这一目标。 AutoMapper不是映射单个实体,而是销毁现有的实体集合并创建具有相同属性的新集合。我确信它适用于某些应用程序,但是使用Entity Framework的更改跟踪,它告诉数据库删除现有记录并使用新ID插入新记录。不幸的是,似乎必须使用我在原始问题中发布的方法单独/手动映射集合。不过,我没有为每个集合重复这一点,而是编写了一个通用处理程序,它将使用指定的键将模型集合映射到实体集合 - 而不会破坏实体:

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的映射,然后使用上面的方法:

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