使用 Automapper,将 DTO 映射回实体框架,包括引用的实体
Posted
技术标签:
【中文标题】使用 Automapper,将 DTO 映射回实体框架,包括引用的实体【英文标题】:Using Automapper, mapping DTOs back to Entity Framework including referenced entities 【发布时间】:2012-12-18 04:50:23 【问题描述】:我有使用 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 来映射域 - > DTO,使用它来做相反的事情会更优雅,因为我的 DTO在很大程度上类似于我的域对象。
是否有教科书式的方法来处理带有 EF 的 Automapper,以及具有导航属性的域对象/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
我猜它又回到了手动操作。希望这可以帮助其他有同样疑问的人。
【问题讨论】:
在您的ClientDto
班级中应该是ICollection<ClientLocationDto>
和ICollection<NoteDto>
吗? (你也有ComplianceRequirementDto
)。你映射这些 Dto 的方式和 ClientDto 一样吗?
@GertArnold 是的,谢谢,您是正确的,但这与问题无关。我正在研究一种解决原始问题的标准化方法,我可能会在准备好后将其发布为答案。
@Ibraheem 您的标准化方式有什么进展吗?感觉需要更多 Automapper 地图,并且使用反射在更新中的导航属性中进行某种递归移动。
嘿!只是想知道您是否有机会在下面尝试我的答案?如果是这样,您能否将其标记为正确答案?非常感谢!!
【参考方案1】:
您已经发现了问题:
上面的持久化逻辑更新了 'root' EF 代理对象在 上下文,但不会更新任何相关实体
您仅在根节点上设置修改状态。您必须编写代码来遍历所有对象并将状态设置为已修改。
【讨论】:
正如我所说,“回到手动操作”。 “这是 EF 的核心限制”。 Automapper 将无法提供帮助。【参考方案2】:我实现了一个模式来使用 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();
这样,所有子对象的层次结构都会在数据库中相应更新。
【讨论】:
【参考方案3】:您要做的是首先从数据库中获取实体:
var centity = _context.Client.First(a=>a.Id = id)
然后你映射并更新(这是你要找的,它只会映射它在 inputDTO 中找到的东西,而不管其他属性)
Mapper.Map<UpdateClientInput, Client>(inputDto, centity);
_context.update();
【讨论】:
以上是关于使用 Automapper,将 DTO 映射回实体框架,包括引用的实体的主要内容,如果未能解决你的问题,请参考以下文章
[使用Automapper时,我是否也应该展平/映射视图模型的内部objetc?