我使用存储库模式来提供对聚合的访问和保存。
问题是更新由实体关系组成的聚合。
例如,采用Order
和OrderItem
关系。聚合根为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;
}
}
}
在上面的示例中,只会更新Order
实体,而不会更新其关联的OrderItem
集合。
我是否必须附加所有OrderItem
个实体?我怎么能这样做呢?
答案 0 :(得分:17)
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;
}
};
}
然后更改您的Order
和OrderItem
类以实现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
个状态图)
你可能会发现自己想要更加细化 跟踪修改后的属性。而不是标记整个实体 如果已修改,您可能只需要实际拥有的属性 更改为标记为已修改。 除了将实体标记为已修改之外,客户端也是如此 负责记录哪些属性已被修改。单程 要做到这一点,将添加一个修改后的属性名称列表 状态跟踪界面。
但是由于我的答案已经太长了,如果你想了解更多,请去阅读她book; - )
答案 1 :(得分:7)
我的观点(DDD特定)答案是:
切断数据层的EF实体。
确保您的数据层仅返回域实体(不是EF实体)。
忘掉EF的延迟加载和IQueryable()
善良(读:梦魇)。
考虑使用文档数据库。
不要使用通用存储库。
我发现在EF中要求的唯一方法是首先删除或停用数据库中作为订单子项的所有订单商品,然后添加或重新激活数据库中现在的所有订单商品新更新订单的一部分。
答案 2 :(得分: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,因为我的aggregateroot是产品(不是产品类别),但我想在服务层创建或更新产品时添加产品类别:
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();
}
}
希望有所帮助