EF7:已添加至Context的实体查询返回Null的问题求助
问题
我采用CQRS架构并尝试使用领域事件。在接收新订单命令后,将新建的Order对象添加至DbContext。相关处理代码如下:
public async Task<Guid> Handle(CreateOrderCommand message, CancellationToken cancellationToken) { ... var order = new Order(...); ... order.SubmitOrder(); _orderRepository.Add(order); await _orderRepository.UnitOfWork .SaveEntitiesAsync(cancellationToken); return order.Id; }
order.SubmitOrder()方法实现如下:
public void SubmitOrder() { AddDomainEvent(new OrderPlacedDomainEvent(Guid.NewGuid(), Id)); }
orderRepository.UnitOfWork.SaveEntitiesAsync是UnitOfWork的重载方法,代码如下:
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default) { // Dispatch Domain Events collection. // Choices: // A) Right BEFORE committing data (EF SaveChanges) into the DB will make a single transaction including // side effects from the domain event handlers which are using the same DbContext with "InstancePerLifetimeScope" or "scoped" lifetime // B) Right AFTER committing data (EF SaveChanges) into the DB will make multiple transactions. // You will need to handle eventual consistency and compensatory actions in case of failures in any of the Handlers. if (_mediator != null) { await _mediator.DispatchDomainEventsAsync(this); } // After executing this line all the changes (from the Command Handler and Domain Event Handlers) // performed through the DbContext will be committed await base.SaveChangesAsync(cancellationToken); return true; }
注:事件分发及处理器调用前并未保存数据。
现在在事件处理器中,尝试从Context获取Order对象:
await _context.Orders.Include(o => o.OrderItems).SingleOrDefaultAsync(o => o.Id == id, cancellationToken: cancellationToken);
该查询返回null,但在_context.ChangeTracker.DebugView.LongView中可看到该数据。请问有办法在此处获取该Order数据吗?
解决方案
问题出在EF Core默认查询会直接访问数据库,而此时Order仅存在于ChangeTracker中,还未持久化到数据库,所以直接查询DbSet返回null。以下几种方式可以获取到目标Order:
方法1:从ChangeTracker中直接筛选
直接遍历ChangeTracker里的已跟踪实体,找到新增状态的Order:
var order = _context.ChangeTracker.Entries<Order>() .Where(e => e.Entity.Id == id && e.State == EntityState.Added) .Select(e => e.Entity) .SingleOrDefault(); // 如果需要关联的OrderItems,确保它们已被跟踪,直接访问实体的OrderItems属性即可 if (order != null) { // 访问order.OrderItems,EF Core会自动加载已跟踪的关联数据 }
方法2:结合本地实体查询
使用DbSet的Local属性先查找本地已跟踪的实体,找不到再查数据库(适合兼容已存在实体的场景):
var order = _context.Orders.Local .FirstOrDefault(o => o.Id == id) ?? await _context.Orders.Include(o => o.OrderItems) .SingleOrDefaultAsync(o => o.Id == id, cancellationToken);
方法3:调整事件分发时机(可选)
如果业务允许,可以先保存数据到数据库再分发事件,修改SaveEntitiesAsync的执行顺序:
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default) { // 先提交数据到数据库 await base.SaveChangesAsync(cancellationToken); // 再分发领域事件 if (_mediator != null) { await _mediator.DispatchDomainEventsAsync(this); } return true; }
这种方式下事件处理器直接查询数据库就能拿到Order,但会变成多事务模式,需要处理最终一致性和失败补偿逻辑,对应原有代码注释中的选项B。
内容的提问来源于stack exchange,提问作者Azarakhsh Hamedi
相关产品推荐
相关产品推荐

