使用Automapper将DTO映射到实体

16

我有一个以下结构的Entity Framework POCO。

public class Entity
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
}

我已经为这个实体创建了一个数据传输对象,以供我的视图使用。

public class EntityDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}
现在,我在我的Global.asax文件中有以下映射代码。

Mapper.CreateMap<Entity, EntityDto>();
Mapper.CreateMap<EntityDto, Entity>(); // not sure whether I need this as well?

一切工作正常,我可以将DTO传递到我的视图并从我的EntityDto模型创建一个新的Entity实例。但是当我尝试编辑Entity时出现问题。我知道这是因为AutoMapper丢失了EF创建用于跟踪对象更改的Entity Key,但是在阅读了几个来源之后,似乎没有明确的解决方案。这是我用来编辑实体的操作。

public ActionResult EditEntity(EntityDto model)
{
    var entity = context.Entities.Single(e => e.Id == model.Id);
    entity = Mapper.Map<EntityDto, Entity>(model); // this loses the Entity Key stuff
    context.SaveChanges();

    return View(model);
}

现在,我该怎么做才能解决这个问题?我可以:

  1. 以某种方式告诉AutoMapper忽略实体键属性吗?
  2. 让AutoMapper复制实体键属性吗?
  3. 使用.Attach()将映射的Entity附加并将状态设置为修改?

非常感谢任何帮助。

3个回答

25

尝试将实体作为第二个参数传递给您的映射。

entity = Mapper.Map<EntityDto, Entity>(model, entity);
否则,你的实体实例将被覆盖为一个新实例,并且你会失去在第一行创建的实体。

很不幸,我已经尝试过这个了,结果出现了相同的问题 - 基本上 AutoMapper 表示它无法找到映射配置,因为当我从上下文中检索我的 Entity 时,它不再是普通的 POCO,所以 AutoMapper 不知道该怎么做。 - Paul Aldred-Bann
这是问题,因为在我创建AutoMapper映射时,Entity只是一个POCO,所以它不包含在检索时EF创建的任何额外实体信息,所以我无法告诉AutoMapper忽略它们。 - Paul Aldred-Bann
那么第一行返回的实体不是Entity类型,请将鼠标悬停在“var”上查看其类型,并为该类型创建映射。此外,你需要的是第二个映射<dto--> entity>,而不是第一个。 - dark_ruby
2
天啊,我不知道 Map 还能这样用。太神奇了。 - Worthy7
1
@dark_ruby非常感谢,它解决了我的问题。 - AminM
显示剩余2条评论

11

如何将我的映射实体附加到上下文并将其状态设置为已修改?

public ActionResult EditEntity(EntityDto model)
{
    var entity = Mapper.Map<Entity>(model);
    context.Set<Entity>().Attach(entity); // (or context.Entity.Attach(entity);)
    context.Entry<Entity>(entity).State = System.Data.EntityState.Modified;
    context.SaveChanges();
    return View(model);
}

你的上下文在哪里实例化?我认为你应该在EditEntity操作中执行。

public ActionResult EditEntity(EntityDto model)
{
    using(var context = new MyContext())
    {
        var entity = Mapper.Map<Entity>(model);
        context.Set<Entity>().Attach(entity); // (or context.Entity.Attach(entity);)
        context.Entry<Entity>(entity).State = System.Data.EntityState.Modified;
        context.SaveChanges();
        return View(model);
    }
}

太棒了,这刚刚起作用了。给我一些时间,在我的实际项目中尝试一下。 - Paul Aldred-Bann
1
很高兴我能够使用alt-tab和复制粘贴来帮助你。 :) - Pluc
你不应该直接在展示层中使用EF实体,这是一个经典的不分离关注点的例子。 - user692942
1
@user692942 完全同意你的观点。10年前,我没有好的编码实践。现在,我会将那个逻辑封装在数据层中(可能是在存储库模式中),从而不会将EF模型暴露给表示层。然而,问题不是关于代码结构,而是关于如何使用来自表示层的传入DTO对象并更新实体。 - Pluc
我也可能避免假定DTO对象具有数据模型的所有属性。 我更喜欢从数据库加载模型,然后使用automapper仅更新DTO具有映射的字段,然后保存上下文。 我建议的重构/新功能很危险。如果您在数据模型/数据库中添加一个字段/列,并忘记将其添加到DTO /表示层(或不适用于该视图),则我发布的代码将清空该列/将其设置为默认值。 - Pluc

4

不需要使用Automapper进行DTO到实体转换的另一种方法是使用DbEntry:

        var oldEntity = DbSet.FirstOrDefault(x => x.Id == updatedEntity.Id);
        var oldEntry = Context.Entry(oldEntity);

        oldEntry.CurrentValues.SetValues(updatedEntity);

获取旧实体之后,您不需要进行任何附加/状态检查,因为它已经有了变更跟踪。此外,CurrentValues.SetValues可以接受不同类型,例如在此示例中updatedEntity是DTO。Set Values文档的解释如下:
将此字典的值设置为从给定对象读取的值。给定对象可以是任何类型。对象上具有与字典中属性名称相匹配且可读的属性将被读取。其他属性将被忽略。这允许从简单的数据传输对象(DTO)复制属性,例如。
因此看起来它已经可以以自动映射方式执行。

这对我完全奏效了。Mapper.Map(model, entity) 的解决方案对我不起作用,因为当您更新嵌套实体(因此您的属性现在引用不同的(已跟踪)实体)时,自动映射器解决方案将“覆盖”先前引用的实体,并且更改跟踪器会抱怨它已经正在跟踪具有该标识符的实体。 - nicolaas
当你的EF渗入展示层时,就会发生这种情况。 - user692942

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接