entity framework和领域驱动设计

尝试使用EF和DDD设置一个简单的应用程序几天后,我不得不说我感到非常沮丧,并认为我最好使用Linq-to-SQL并忘记所有DDD和EF。

有了EF

a)您不能拥有适当的只读集合

b)当您从子项集合中删除某些内容时,您经常会得到该关系无法更改该关系,因为一个或多个外键属性是不可为空的消息

c)没有简单的方法可以删除父项的所有子项并重新插入它们

鉴于我发现的解决方法非常令人讨厌,所有这些对我来说都是显而易见的。 有人设法组建了一个解决这些问题的简单存储库吗?

如果是的话,你是否愿意分享一些代码?!?

此外,我知道这是一个很大的话题,有没有人在大型Web应用程序中有任何实际DDD优势的经验? 我们都知道这个理论,但如果真的值得麻烦的话,想一个想法会很好!


好吧,到目前为止我能做的最好而不必做各种各样的漫游变通办法就是在我查询时使用AsNoTracking()。 这样我就得到了我的信息,而EF离开时却没有做任何背后的事情。 我现在可以从一个集合中删除,我也可以删除(谁会认为id必须回到sql!)有谁知道使用AsNoTracking的任何陷阱? 至于我可以根据我的对象生成SQL并填充它们或更新/删除它我很好。 整个跟踪事情无论如何都走得太远了?


namespace EShop.Models.Repositories { public class CustomerRepository : BaseRepository, IRepository { public CustomerRepository() : base(new EShopData()) { } #region CoreMethods public void InsertOrUpdate(Customer customer) { if (customer.CustomerId > 0) { // you cannot use remove, if you do you ll attach and then you ll have issues with the address/cards below // dbContext.Entry(address).State = EntityState.Added; will fail dbContext.Database.ExecuteSqlCommand("DELETE FROM CustomerAddress WHERE CustomerId = @CustomerId", new SqlParameter("CustomerId", customer.CustomerId)); dbContext.Database.ExecuteSqlCommand("DELETE FROM CreditCard WHERE CustomerId = @CustomerId", new SqlParameter("CustomerId", customer.CustomerId)); foreach (var address in customer.Addresses) dbContext.Entry(address).State = EntityState.Added; foreach (var card in customer.CreditCards) dbContext.Entry(card).State = EntityState.Added; dbContext.Entry(customer).State = EntityState.Modified; } else { dbContext.Entry(customer).State = EntityState.Added; foreach (var card in customer.CreditCards) dbContext.Entry(card).State = EntityState.Added; foreach (var address in customer.Addresses) dbContext.Entry(address).State = EntityState.Added; } } public void Delete(int customerId) { var existingCustomer = dbContext.Customers.Find(customerId); if (existingCustomer != null) { //delete cards var creditCards = dbContext.CreditCards.Where(c => c.CustomerId == customerId); foreach (var card in creditCards) dbContext.Entry(card).State = EntityState.Deleted; //delete addresses var addresses = dbContext.CustomerAddresses.Where(c => c.CustomerId == customerId); foreach (var address in addresses) dbContext.Entry(address).State = EntityState.Deleted; //delete basket dbContext.Entry(existingCustomer).State = EntityState.Deleted; } } public Customer GetById(int customerId) { return dbContext.Customers.Include("Addresses").AsNoTracking().SingleOrDefault(c => c.CustomerId == customerId); } public IList GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection) { return null; } public void Save() { dbContext.SaveChanges(); } #endregion CoreMethods #region AdditionalMethods #endregion AdditionalMethods } 

}

对b的响应:创建数据库时,必须级联删除(即数据库也删除所有相关的子记录)或使外键可以为空。 那你就不会得到那个错误。 这不应该归咎于EF,它是关系数据库处理约束的方式。 您可以在EDMX中配置它,首先在代码中配置它,或在数据库端使用DDL配置它。 根据您的决定如何设置项目。

对c的回应:更多的是一般感觉,但删除所有孩子并重新插入声音非常容易出错并且有“气味”。 至少我会这样做只有在绝对需要时才这样做。 从性能的角度来看,更新可能更快。 也许您可以重新考虑选择删除和​​重新插入的问题?

好吧,我认为我现在已经有足够的这个了,所以我将总结一下我相当消极的经历

a)这是有可能的,但由于这是第5版,我期待更好的东西。 可能最简单,最简单的解决方法可以在这里找到http://edo-van-asseldonk.blogspot.co.uk/2012/03/readonly-collections-with-entity.html或者我想你甚至可以想出你自己的特定于手头问题的只读集合,例如BasketProductsReadOnlyCollection,如果你有一个篮子和它的产品集合。

b)反正可能我们不必担心。 在“天才之笔”中,微软公司几乎不可能在这里找到适当的DDD代码。 如果您的产品表中有一个篮子和带有BasketId的产品不可为空,那么如果您使用Basket.RemoveProduct(产品),则会遇到麻烦。 删除这样的东西意味着删除“关系”而不是记录。 所以EF会尝试将BasketId设置为null,如果它不能抛出一个exception(并且我不想让它只能适合EF,即使我想要如果我和一个DBA一起工作,不管怎样?)你是什么人需要做的是调用dbContext.Products.Remove(product)以确保它被删除。 这基本上意味着您的业务逻辑代码需要了解dbContext

c)我不能再烦了! 在StackOverflow上有关于此的响应,你可能会得到一些启动和运行但它不应该那么困难和反直觉。

至于更大的图景,我看了一下与“独立”实体合作的N层建议。 我读了Julia Lerman的一本书,他似乎是这个主题的权威,我没有留下深刻的印象。 整个附加对象图的工作方式以及推荐的处理方法再次非常直观。 她推荐的使事情变得“简单”的方法是让每个对象在你的业务代码中记录它的状态! 不是我喜欢的。

我不认为自己是一个建筑天才或其他东西,也许我错过了一些东西(或很多),但对我来说EF的努力似乎是错位的。 他们花了很多时间和金钱来实现整个跟踪系统,它应该为你做所有事情(典型的MS,他们认为我们太愚蠢或者有些东西可以照顾我们自己的东西)而不是专注于其他可能使这个产品成为可能的东西更容易使用。

我想从我的ORM中获取的是在我的对象中为我提供数据,然后让我独自以任何我想要的方式处理它们然后我想将我的对象或对象图传递回ORM并且可以自由地告诉我它是我想要从对象图中添加/删除/更新的,以及如何在没有EF的当前恶作剧的情况下。

一句话:我想我会给MS更多年的时间,他们最终可能会把它弄好但是这还不适合我。 MS最终会在他们的网站上放一些适当的文档/教程吗? 我记得几年前在NHibernate上阅读了300页的PDF教程。

如果其他人都在努力解决这个问题,这是我能想到的最佳实现,看看RemoveFromBasket,AddToBasket方法,不理想,但至少你得到了一些东西并且正在运行

  using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Helpers; using EShop.Models.DomainModel; using System.Data; using EShop.Models.DataAccess; using System.Data.Objects; using System.Data.Entity.Infrastructure; namespace EShop.Models.Repositories { public class BasketRepository : BaseRepository, IRepository { public BasketRepository() : base(new EShopData()) { } #region CoreMethods public void InsertOrUpdate(Basket basket) { var basketInDB = dbContext.Baskets.SingleOrDefault(b => b.BasketId == basket.BasketId); if (basketInDB == null) dbContext.Baskets.Add(basket); } public void Delete(int basketId) { var basket = this.GetById(basketId); if (basket != null) { foreach (var product in basket.BasketProducts.ToList()) { basket.BasketProducts.Remove(product); //delete relationship dbContext.BasketProducts.Remove(product); //delete from DB } dbContext.Baskets.Remove(basket); } } public Basket GetById(int basketId) { // eager-load product info var basket = dbContext.Baskets.Include("BasketProducts") .Include("BasketProducts.Product.Brand").SingleOrDefault(b => b.BasketId == basketId); return basket; } public IList GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection) { throw new NotImplementedException(); } public void Save() { dbContext.SaveChanges(); } #endregion CoreMethods #region AdditionalMethods public void AddToBasket(Basket basket, Product product, int quantity) { var existingProductInBasket = dbContext.BasketProducts.Find(basket.BasketId, product.ProductId); if (existingProductInBasket == null) { var basketProduct = new BasketProduct() { BasketId = basket.BasketId, ProductId = product.ProductId, Quantity = quantity }; basket.BasketProducts.Add(basketProduct); } else { existingProductInBasket.Quantity = quantity; } } public void RemoveFromBasket(Basket basket, Product product) { var existingProductInBasket = dbContext.BasketProducts.Find(basket.BasketId, product.ProductId); if (existingProductInBasket != null) { basket.BasketProducts.Remove(existingProductInBasket); //delete relationship dbContext.BasketProducts.Remove(existingProductInBasket); //delete from DB } } public void RemoveFromBasket(BasketProduct basketProduct) { var basket = dbContext.Baskets.Find(basketProduct.BasketId); var existingProductInBasket = dbContext.BasketProducts.Find(basketProduct.BasketId, basketProduct.ProductId); if (basket != null && existingProductInBasket != null) { basket.BasketProducts.Remove(existingProductInBasket); //delete relationship dbContext.BasketProducts.Remove(existingProductInBasket); //delete from DB } } public void ClearBasket(Basket basket) { foreach (var product in basket.BasketProducts.ToList()) basket.BasketProducts.Remove(product); } #endregion AdditionalMethods } 

}

好吧,看起来我已经设法使用EF 5或多或少按照我想要的方式处理所有事情。 问题b似乎适用于EF5。 我认为我现在有一个合适的DDD篮子类和一个合适的存储库,所以非常满意,也许我对EF过于苛刻太不公平了!

 public partial class Basket { public Basket() { this.BasketProducts = new List(); } public int BasketId { get; set; } public int? CustomerId { get; set; } public decimal TotalValue { get; set; } public DateTime Created { get; set; } public DateTime Modified { get; set; } public ICollection BasketProducts { get; private set; } public void AddToBasket(Product product, int quantity) { //BUSINESS LOGIC HERE var productInBasket = BasketProducts.SingleOrDefault(b => b.BasketId == this.BasketId && b.ProductId == product.ProductId); if (productInBasket == null) { BasketProducts.Add(new BasketProduct { BasketId = this.BasketId, ProductId = product.ProductId, Quantity = quantity }); } else { productInBasket.Quantity = quantity; } } public void RemoveFromBasket(Product product) { //BUSINESS LOGIC HERE var prodToRemove = BasketProducts.SingleOrDefault(b => b.BasketId == this.BasketId && b.ProductId == product.ProductId); BasketProducts.Remove(prodToRemove); } } 

}

 public class BasketRepository : BaseRepository, IRepository { public BasketRepository() : base(new EShopData()) { } #region CoreMethods //public void InsertOrUpdate(Basket basket, bool persistNow = true) { } public void Save(Basket basket, bool persistNow = true) { var basketInDB = dbContext.Baskets.SingleOrDefault(b => b.BasketId == basket.BasketId); if (basketInDB == null) dbContext.Baskets.Add(basket); if (persistNow) dbContext.SaveChanges(); } public void Delete(int basketId, bool persistNow = true) { var basket = this.GetById(basketId); if (basket != null) { foreach (var product in basket.BasketProducts.ToList()) { basket.BasketProducts.Remove(product); //delete relationship dbContext.BasketProducts.Remove(product); //delete from DB } dbContext.Baskets.Remove(basket); } if (persistNow) dbContext.SaveChanges(); } public Basket GetById(int basketId) { // eager-load product info var basket = dbContext.Baskets.Include("BasketProducts") .Include("BasketProducts.Product.Brand").SingleOrDefault(b => b.BasketId == basketId); return basket; } public IList GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection) { throw new NotImplementedException(); } public void SaveForUnitOfWork() { dbContext.SaveChanges(); } 

}

a)你首先要做的是什么? 你不能把这个集合设为私有,只暴露那些拍摄快照的公共财产吗?

b)要从数据库中删除子实体,请使用dbcontext.ThatEntitySet.Remove(child) ,而不是parent.Children.Remove(child)

或者,您可以通过将子项中的外键作为主键的一部分来建立标识关系。 然后parent.Children.Remove(child)将从DB中删除一行。

c)似乎你在做一些愚蠢的事情。 如果您提供了详细信息,我会提出不同的解决方案

大话题:您的域名是否足够复杂? 或者您只是尝试应用…在一个简单的CRUD应用程序中强制DDD模式? 你有什么商业规则? 不变? 您的实体有哪些方法? 有什么政策吗?

为什么需要InsertOrUpdate方法? 我想你发明了它是因为你只使用相同的forms来创建和更新实体。 这是一个强烈的信号,你只是做一个CRUD应用程序。