领域模型与持久化模型分离时,变更追踪为何成为问题?
DDD领域模型与持久化模型分离中的变更追踪疑问解答
首先,我先解释为什么在使用NHibernate时,这种分离模式会引发变更追踪问题,再结合你提供的代码解答你的两个具体问题。
为什么NHibernate的变更追踪会成为问题?(附实际案例)
NHibernate的核心优势之一就是自动变更追踪——它会在持久化上下文(Session)中缓存加载的对象,记录属性快照,事务提交时自动对比快照和当前对象状态,生成更新语句。但当你把领域模型和持久化模型完全分离后,这个机制很容易失效,举个实际场景:
// 持久化模型 public class OrderPO { public int Id { get; set; } public decimal Amount { get; set; } public OrderStatus Status { get; set; } } // 领域模型 public class OrderEntity { public int Id { get; } public decimal Amount { get; private set; } public OrderStatus Status { get; private set; } public OrderEntity(int id, decimal amount, OrderStatus status) { Id = id; Amount = amount; Status = status; } public void MarkAsPaid() { if (Status != OrderStatus.Created) throw new InvalidOperationException(); Status = OrderStatus.Paid; } }
应用服务中的操作:
public void MarkOrderAsPaid(int orderId) { // 从仓储获取持久化模型(NHibernate加载,处于Session追踪中) OrderPO orderPO = _orderRepo.GetById(orderId); // 映射到领域模型 OrderEntity orderEntity = new OrderEntity(orderPO.Id, orderPO.Amount, orderPO.Status); // 执行领域逻辑 orderEntity.MarkAsPaid(); // 映射回持久化模型(创建新的OrderPO实例) OrderPO updatedOrderPO = new OrderPO { Id = orderEntity.Id, Amount = orderEntity.Amount, Status = orderEntity.Status }; // NHibernate无法自动追踪这个新实例的变更 }
这里的核心问题:
- 新创建的
updatedOrderPO不在NHibernate的Session缓存中,NHibernate完全不知道这个对象的存在,自然不会自动追踪它的变更。你必须手动调用_session.Update(updatedOrderPO)才能让它被追踪,但这样就失去了自动变更追踪的便利性。 - 如果在你加载
orderPO之后,有另一个线程修改了数据库中的订单记录,你的updatedOrderPO基于旧快照更新,会覆盖别人的修改,引发并发问题——而NHibernate的自动追踪会通过乐观锁对比快照和当前数据库状态,能避免这类问题,但手动更新就需要你自己处理乐观锁逻辑。
如果为了利用追踪而复用原来的orderPO实例,手动同步领域模型的变更:
orderPO.Status = orderEntity.Status;
这又违背了领域模型与持久化模型分离的初衷——你需要在应用层维护两者的属性同步,代码会变得繁琐,一旦领域模型新增属性,很容易遗漏同步,导致数据不一致。
解答你的两个具体问题
Q1: 这段代码是否会丢失ORM的优势(如变更追踪)?
先看你的代码:
//Application Service Ticket ticket = applicationService.GetTicket(1); ticket.Cost = ..... TicketEntity ticketEntity = AutoMapper.Map<TicketEntity>(ticket); ticketEntity.DomainMethod(); ticket = AutoMapper.Map<Ticket>(ticketEntity);
答案是会丢失自动变更追踪的优势,原因如下:
- 最初的
ticket是NHibernate从Session加载的,处于被追踪状态,但你修改ticket.Cost后,立刻把它映射到TicketEntity。 - 当你用AutoMapper把
TicketEntity映射回ticket时,默认情况下AutoMapper会创建一个新的Ticket实例(而非复用原来的被追踪实例)。这个新实例不在NHibernate的Session缓存中,NHibernate不会自动追踪它的变更。 - 如果你想持久化这个新的
ticket,必须手动调用仓储的Update方法或Session.Update(),这就失去了NHibernate自动提交变更的便利性。 - 来回映射的过程中,如果AutoMapper的配置有遗漏(比如某个属性没映射),会导致数据丢失,增加出错风险。
Q2: NHibernate如何追踪变更?如何识别持久化对象对应数据库中的记录?
NHibernate的变更追踪完全依赖于持久化上下文(Session),具体逻辑是:
- 当你通过
GetById、Load等方法从Session加载对象时,NHibernate会把这个对象的实例缓存到Session中,同时记录该对象加载时的属性快照(即加载时各属性的原始值)。 - 当事务提交时,NHibernate会遍历Session中所有被追踪的对象,对比当前属性值和快照,找出变更的属性,生成对应的Update语句。
NHibernate识别对象对应数据库记录的方式,不仅仅依赖ID:
- 对象实例引用:同一个Session中,同一个数据库ID的对象只会存在一个实例。比如你两次调用
GetById(1),得到的是同一个Ticket对象,不会重复查询数据库。 - 实体标识符(ID)+ 映射元数据:NHibernate会结合实体的ID和映射的表信息,确定对象对应数据库中的哪条记录。对于复合主键的实体,会用整个复合键来识别。
- Session缓存关联:只有在Session缓存中的对象,才会被NHibernate追踪。如果是你手动创建的新对象(即使ID和数据库记录一致),NHibernate不会自动关联它,除非你调用
Session.Update()或Session.Merge()把它纳入Session管理。
举个直观的例子:
using (var session = _sessionFactory.OpenSession()) using (var tx = session.BeginTransaction()) { // 从Session加载,被追踪 Ticket ticket1 = session.Get<Ticket>(1); // 同一个Session中,获取的是同一个实例 Ticket ticket2 = session.Get<Ticket>(1); Console.WriteLine(ticket1 == ticket2); // 输出True // 修改被追踪的实例 ticket1.Cost = 100; // 事务提交时,NHibernate自动对比快照,发现Cost变更,生成Update语句 tx.Commit(); }
内容的提问来源于stack exchange,提问作者w0051977
相关产品推荐
相关产品推荐

