用于更新整个聚合的通用存储库

Dav*_*New 13 domain-driven-design entity-framework repository-pattern onion-architecture entity-framework-6

我正在使用存储库模式来提供对聚合的访问和保存.

问题是更新由实体关系组成的聚合.

例如,采取OrderOrderItem关系.聚合根Order管理自己的OrderItem集合.OrderRepository因此,一个人将负责更新整个聚合(没有OrderItemRepository).

使用Entity Framework 6处理数据持久性.

更新存储库方法(DbContext.SaveChanges()在别处发生):

public void Update(TDataEntity item)
{
    var entry = context.Entry<TDataEntity>(item);

    if (entry.State == EntityState.Detached)
    {
        var set = context.Set<TDataEntity>();

        TDataEntity attachedEntity = set.Local.SingleOrDefault(e => e.Id.Equals(item.Id));

        if (attachedEntity != null)
        {
            // If the identity is already attached, rather set the state values
            var attachedEntry = context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(item);
        }
        else
        {
            entry.State = EntityState.Modified;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,仅Order更新实体,而不更新其关联的OrderItem集合.

我是否必须附上所有OrderItem实体?我怎么能这样做呢?

Max*_*xSC 18

Julie Lerman在她的" 编程实体框架:DbContext"一书中提供了一种处理如何更新整个聚合的好方法.

正如她写道:

当断开的实体图到达服务器端时,服务器将不知道实体的状态.您需要提供一种方法来发现状态,以便可以使上下文了解每个实体的状态.

这种技术被称为painting the state.

主要有两种方法:

  • 使用您对模型的了解迭代图表并为每个实体设置状态
  • 构建跟踪状态的通用方法

第二个选项非常好,包括创建一个模型中的每个实体都将实现的接口.Julie使用一个IObjectWithState接口来告诉实体的当前状态:

 public interface IObjectWithState
 {
  State State { get; set; }
 }
 public enum State
 {
  Added,
  Unchanged,
  Modified,
  Deleted
 }
Run Code Online (Sandbox Code Playgroud)

首先要做的是Unchanged通过挂钩在Context类中添加构造函数的事件,自动设置从DB检索到的每个实体:

public YourContext()
{
 ((IObjectContextAdapter)this).ObjectContext
  .ObjectMaterialized += (sender, args) =>
 {
  var entity = args.Entity as IObjectWithState;
  if (entity != null)
  {
   entity.State = State.Unchanged;
  }
 };
}
Run Code Online (Sandbox Code Playgroud)

然后更改您的OrderOrderItem类以实现IObjectWithState接口并调用ApplyChanges接受根实体作为参数的方法:

private static void ApplyChanges<TEntity>(TEntity root)
 where TEntity : class, IObjectWithState
{
 using (var context = new YourContext())
 {
  context.Set<TEntity>().Add(root);

  CheckForEntitiesWithoutStateInterface(context);

  foreach (var entry in context.ChangeTracker
  .Entries<IObjectWithState>())
  {
   IObjectWithState stateInfo = entry.Entity;
   entry.State = ConvertState(stateInfo.State);
  }
  context.SaveChanges();
 }
}

private static void CheckForEntitiesWithoutStateInterface(YourContext context)
{
 var entitiesWithoutState =
 from e in context.ChangeTracker.Entries()
 where !(e.Entity is IObjectWithState)
 select e;

 if (entitiesWithoutState.Any())
 {
  throw new NotSupportedException("All entities must implement IObjectWithState");
 }
}
Run Code Online (Sandbox Code Playgroud)

最后但并非最不重要的是,不要忘记在调用之前设置图形的正确状态;-) ApplyChanges (您甚至可以在同一图表中混合ModifiedDeleted状态)

朱莉建议在他的书中更进一步:

你可能会发现自己想要更加细化跟踪修改属性的方式.您可能只希望将实际更改的属性标记为已修改,而不是将整个实体标记为已修改.除了将实体标记为已修改之外,客户还负责记录已修改的属性.一种方法是将修改后的属性名称列表添加到状态跟踪界面.

但是,由于我的答案已经太长了,如果你想了解更多信息,请阅读她的 ;-)


Adr*_*ips 7

我的见解(DDD特定)答案是:

  1. 切断数据层的EF实体.

  2. 确保您的数据层仅返回域实体(不是EF实体).

  3. 忘掉IQueryable()EF 的懒惰和善良(读:梦魇).

  4. 考虑使用文档数据库.

  5. 不要使用通用存储库.

我发现在EF中执行的任务的唯一方法是首先删除或停用数据库中作为订单子项的所有订单项,然后添加或重新激活数据库中现在属于您的所有订单项最近更新的订单.