使用Automapper,将DTO映射回实体框架,包括引用的实体

时间:2013-01-03 19:06:39

标签: entity-framework asp.net-web-api automapper dto navigation-properties

我有使用Entity Framework 5持久保存的POCO域实体。它们是使用存储库模式从DbContext获取的,并通过UoW模式暴露给RESTful MVC WebApi应用程序。 POCO实体是代理并且是延迟加载的。

我在将实体发送到客户端之前将其转换为DTO。我正在使用Automapper执行此操作,它似乎可以正常工作,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对象,但是它的相关实体/导航属性被替换为具有从DTO复制的属性值的新(非代理)POCO实体。

上面,uow.Update()基本上是指一个执行持久化逻辑的函数:

    _context.Entry<T>(entity).State = System.Data.EntityState.Modified;
    _context.SaveChanges();

上述内容并不会持久存在,更不用说相关的实体了。我已经尝试了映射的变体和持久使用分离/状态的不同方法,但总是得到“具有相同键的对象已存在于ObjectStateManager”异常。

我已经看过无数其他线程,但是无法使用Automapper。我可以从上下文中获取代理对象并手动浏览属性,从DTO中更新它们,但是我使用Automapper来映射域 - &gt; DTO和使用它做反过来会更加优雅,因为我的DTO在很大程度上类似于我的域对象。

是否有教科书使用EF处理Automapper的方法,其中Domain Objects / DTO具有导航属性,同时还需要更新?

更新

    var originalEntity = _entities.Find(entity.Id);
    _context.Entry<T>(originalEntity).State = System.Data.EntityState.Detached;
    _context.Entry<T>(entity).State = System.Data.EntityState.Modified;

上述持久性逻辑更新上下文中的“根”EF代理对象,但不更新任何相关实体。我猜这是因为它们没有映射到EF代理对象而是映射到纯域对象。非常感谢帮助!

更新 似乎我试图实现的目标实际上不可能使用当前版本的EF(5)并且这是EF的核心限制而不是与Automapper有关:

Link

Link

我想这是手动完成的。希望这可以帮助那些想知道相同的人。

3 个答案:

答案 0 :(得分:0)

您已经确定了问题所在:

  

上面的持久性逻辑更新了“根”EF代理对象   上下文,但任何相关实体都不会更新

您只在根节点上设置修改后的状态。您必须编写代码来遍历所有对象并将状态设置为已修改。

答案 1 :(得分: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();

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

答案 2 :(得分:0)

您要做的是首先从数据库中获取实体:

var centity = _context.Client.First(a=>a.Id = id)

然后你映射并更新(这是你要找的东西,它只会映射它在inputDTO中找到的东西,并保留其他属性)

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