使用Automapper,将DTO映射回Entity Framework,包括引用实体。

12

我有使用Entity Framework 5持久化的POCO领域实体。它们通过仓储模式从DbContext获取,并通过UoW模式向RESTful MVC WebApi应用程序公开。这些POCO实体是代理对象并且是惰性加载的。

在向客户端发送它们之前,我将我的实体转换为DTO。我正在使用Automapper来完成这个操作,并且它似乎能够很好地将代理POCO映射到DTO,保留导航属性。我正在使用以下映射进行此操作:

    Mapper.CreateMap<Client, ClientDto>();

领域/数据传输对象(DTO)对象的示例:

[Serializable]
public class Client : IEntity
{
    public int Id { get; set; }

    [Required, MaxLength(100)]
    public virtual string Name { get; set; }

    public virtual ICollection<ClientLocation> ClientLocations { get; set; }

    public virtual ICollection<ComplianceRequirement> DefaultComplianceRequirements { get; set; }

    public virtual ICollection<Note> Notes { get; set; }
}

public class ClientDto : DtoBase
{
    public int Id { get; set; }

    [Required, MaxLength(100)]
    public string Name { get; set; }

    public ICollection<ClientLocation> ClientLocations { get; set; }

    public ICollection<ComplianceRequirementDto> DefaultComplianceRequirements { get; set; }

    public ICollection<Note> Notes { get; set; }
}

现在我正在尝试使用从线路发送回来的DTO更新我的上下文。我在使导航属性/相关实体正常工作方面遇到了具体的困难。我使用的映射如下:

    Mapper.CreateMap<ClientDto, Client>()
        .ConstructUsing((Func<ClientDto, Client>)(c => clientUow.Get(c.Id)));

以上,clientUow.Get()是指DbContext.Set.Find(),这样我就从EF中获取了跟踪的代理POCO对象(它还包含所有相关实体的代理)。

在我的控制器方法中,我正在执行以下操作:

    var client = Mapper.Map<ClientDto, Client>(clientDto);
    uow.Update(client);

客户端成功映射为代理POCO对象,但其相关实体/导航属性被替换为一个新的(非代理)POCO实体,并且其属性值从DTO复制而来。

上面提到的uow.Update()基本上是指执行持久化逻辑的函数,我的代码如下:

    _context.Entry<T>(entity).State = System.Data.EntityState.Modified;
    _context.SaveChanges();
上述问题甚至不能持久化实体,更不用说相关的实体了。我已经尝试了映射方案的变化和不同的分离/状态持久化方式,但总是会收到“在ObjectStateManager中已存在具有相同键的对象”异常。
我查看了无数其他线程,但是无法让Automapper完全工作。我可以从上下文中获取代理对象,并手动遍历属性,从DTO中更新它们,但是我正在使用Automapper将领域-> DTO进行映射,使用它来执行相反的操作会更加优雅,因为我的DTO在很大程度上类似于我的领域对象。
是否有一种标准方法来处理带有导航属性的领域对象/DTO的Automapper与EF的结合? 更新:
    var originalEntity = _entities.Find(entity.Id);
    _context.Entry<T>(originalEntity).State = System.Data.EntityState.Detached;
    _context.Entry<T>(entity).State = System.Data.EntityState.Modified;

以上持久性逻辑更新了上下文中的“root”EF代理对象,但是任何相关实体都没有被更新。我猜这是因为它们没有映射到EF代理对象而是普通的领域对象。非常感谢你的帮助!

更新: 看起来我正在尝试实现的实际上在当前版本的EF(5)中不可能实现,这是EF的核心限制,与Automapper无关:

链接

链接

我想这又回到了手动操作的方式。希望这可以帮助其他有同样疑问的人。


你的 ClientDto 类里应该使用 ICollection<ClientLocationDto>ICollection<NoteDto> 吗?(因为你也有 ComplianceRequirementDto)。你映射这些 Dto 的方式和 ClientDto 一样吗? - Gert Arnold
@GertArnold 是的,谢谢你,你是正确的,但这与问题无关。我正在研究一种标准化的方法来解决原始问题,当准备好时,我可能会将其发布为答案。 - Ibraheem
@Ibraheem,你的标准化方式有进展了吗?感觉需要更多Automapper映射和一些使用反射进行Update中导航属性递归移动的方法。 - Josh Gallagher
嗨!我想知道你有没有试过我的下面的答案?如果是这样,能否将其标记为正确答案?非常感谢!! - Tim Norris
3个回答

1
你想要做的是先从数据库中获取实体:
var centity = _context.Client.First(a=>a.Id = id)

然后你可以对此进行映射并更新(这就是你要找的东西,它只会映射输入DTO中找到的内容,并保留其他属性不变)

Mapper.Map<UpdateClientInput,  Client>(inputDto, centity);
_context.update();

0

您已经确定了问题:

上述持久化逻辑更新了上下文中的“根”EF代理对象,但是任何相关实体都没有被更新

您只设置了根节点的修改状态。您必须编写代码来迭代所有对象并将状态设置为修改。


正如我所说,“回到手动操作”。这是EF的核心限制。Automapper无法提供帮助。 - Ibraheem

0

我使用了一种模式来处理带有EF的层次结构模型状态。

每个实体模型类都实现了以下接口,视图模型类也是如此:

public interface IObjectWithState
{
    ObjectState ObjectState { get; set; }
}

下面定义了ObjectState枚举:

public enum ObjectState
{
    Unchanged  = 0,
    Added = 1,
    Modified = 2,
    Deleted = 3
}

例如,在使用EF保存深层次对象层次结构时,我将视图模型对象映射到它们对应的实体对象,包括ObjectState。
然后,我将根实体对象附加到上下文中(因此也包括所有子对象):
dbContext.MyCustomEntities.Attach(rootEntityObj);

然后我在DbContext上拥有一个扩展方法,它循环遍历上下文更改跟踪器中的所有项目,并更新每个实体的状态(就像您上面所做的那样)。

    public static int ApplyStateChanges(this DbContext context)
    {
        int count = 0;
        foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
        {
            IObjectWithState stateInfo = entry.Entity;
            entry.State = ConvertState(stateInfo.ObjectState);
            if (stateInfo.ObjectState != ObjectState.Unchanged)
                count++;
        }
        return count;
    }

然后我们可以像平常一样保存更改:

dbContext.SaveChanges();

这样,所有子对象的层次结构都将相应地在数据库中更新。


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