15

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

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

例如,采取OrderandOrderItem关系。聚合根Order管理自己的OrderItem集合。因此AnOrderRepository将负责更新整个聚合(不会有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;
        }
    }
}

在我上面的例子中,只有Order实体会被更新,而不是它的关联OrderItem集合。

我必须附加所有OrderItem实体吗?我怎么能一般地做到这一点?

4

3 回答 3

19

Julie Lerman 在她的书Programming Entity Framework: DbContext中提供了一种处理如何更新整个聚合的好方法。

正如她所写:

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

这种技术被称为painting the state

主要有两种方法可以做到这一点:

  • 使用您对模型的了解遍历图形并为每个实体设置状态
  • 建立一个通用的方法来跟踪状态

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

 public interface IObjectWithState
 {
  State State { get; set; }
 }
 public enum State
 {
  Added,
  Unchanged,
  Modified,
  Deleted
 }

您必须做的第一件事是通过在您的类中Unchanged添加一个连接事件的构造函数来自动将状态设置为从数据库中检索到的每个实体:Context

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

然后更改您的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");
 }
}

最后但同样重要的是,不要忘记在调用之前设置图形实体的正确状态;-) ApplyChanges (您甚至可以在同一个图形中混合Modified和状态。)Deleted

朱莉建议在她的书中走得更远:

您可能会发现自己希望更精细地跟踪修改后的属性。您可能希望仅将实际更改的属性标记为已修改,而不是将整个实体标记为已修改。除了将实体标记为已修改之外,客户端还负责记录哪些属性已被修改。一种方法是将修改后的属性名称列表添加到状态跟踪界面。

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

于 2014-01-29T16:21:45.947 回答
6

我的固执己见(特定于 DDD)的答案是:

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

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

  3. 忘记IQueryable()EF 的延迟加载和优点(阅读:噩梦)。

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

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

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

于 2014-01-29T15:06:04.667 回答
0

所以你在聚合根的更新方法上做得很好,看看这个域模型:

 public class ProductCategory : EntityBase<Guid>
    {
      public virtual string Name { get; set; }
    }

    public class Product : EntityBase<Guid>, IAggregateRoot
    {
    private readonly IList<ProductCategory> _productCategories = new List<ProductCategory>();

    public void AddProductCategory(ProductCategory productCategory)
        {
            _productCategories.Add(productCategory);
        }
    }

它只是一个具有产品类别的产品,我刚刚创建了 ProductRepository,因为我的聚合根是产品(不是产品类别),但我想在服务层中创建或更新产品时添加产品类别:

public CreateProductResponse CreateProduct(CreateProductRequest request)
        {
            var response = new CreateProductResponse();
         try
            {
                var productModel = request.ProductViewModel.ConvertToProductModel();   
                Product product=new Product();
                product.AddProductCategory(productModel.ProductCategory);
                _productRepository.Add(productModel);
                _unitOfWork.Commit();
            }
            catch (Exception exception)
            {
                response.Success = false;
            }
            return response;
        }

我只是想向您展示如何为域中的实体创建域方法并在服务或应用层中使用它。如您所见,下面的代码通过数据库中的 productRepository 添加 ProductCategory 类别:

product.AddProductCategory(productModel.ProductCategory);

现在要更新同一实体,您可以要求 ProductRepository 并获取实体并对其进行更改。请注意,要分别检索和聚合的实体和值对象,您可以编写查询服务或 readOnlyRepository:

 public class BlogTagReadOnlyRepository : ReadOnlyRepository<BlogTag, string>, IBlogTagReadOnlyRepository
    {
        public IEnumerable<BlogTag> GetAllBlogTagsQuery(string tagName)
        {
            throw new NotImplementedException();
        }
    }

希望能帮助到你

于 2014-02-01T20:50:49.237 回答