实体框架和领域驱动设计

时间:2012-12-03 17:40:51

标签: c# entity-framework domain-driven-design

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

使用EF

a)您无法拥有正确的只读集合

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

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

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

如果是,你会很友好地分享一些代码吗?!?

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


好的,我能做的最好的事情就是使用,而不必做各种各样的漫游变通办法 当我查询某事时AsNoTracking()。这样我就可以获得我的信息,而EF则不会这样做 不管它背后是怎么回事。我现在可以从一个集合中删除,我可以 能够删除(谁会认为我必须回到sql!) 有谁知道使用AsNoTracking的任何陷阱?据我所知,我可以基于SQL 在我的对象上填充或更新/删除它们我很好。整个跟踪的事情 反正走得太远了?


namespace EShop.Models.Repositories
{
public class CustomerRepository : BaseRepository, IRepository<Customer, Int32>
{
    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<CustomerAddress>(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<CustomerAddress>(address).State = EntityState.Added;
            foreach (var card in customer.CreditCards)
                dbContext.Entry<CreditCard>(card).State = EntityState.Added;

            dbContext.Entry<Customer>(customer).State = EntityState.Modified;
        }
        else
        {
            dbContext.Entry<Customer>(customer).State = EntityState.Added;
            foreach (var card in customer.CreditCards)
                dbContext.Entry<CreditCard>(card).State = EntityState.Added;
            foreach (var address in customer.Addresses)
                dbContext.Entry<CustomerAddress>(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<CreditCard>(card).State = EntityState.Deleted;

            //delete addresses
            var addresses = dbContext.CustomerAddresses.Where(c => c.CustomerId == customerId);
            foreach (var address in addresses)
                dbContext.Entry<CustomerAddress>(address).State = EntityState.Deleted;

            //delete basket
            dbContext.Entry<Customer>(existingCustomer).State = EntityState.Deleted;
        }
    }

    public Customer GetById(int customerId)
    {
        return dbContext.Customers.Include("Addresses").AsNoTracking().SingleOrDefault(c => c.CustomerId == customerId);
    }

    public IList<Customer> GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection)
    {
        return null;
    }

    public void Save()
    {
        dbContext.SaveChanges();
    }

    #endregion CoreMethods


    #region AdditionalMethods

    #endregion AdditionalMethods

}

}

5 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:1)

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

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,如果它不能抛出异常(和 不,我不想让它只是为了适合EF,即使我想要如果我与DBA合作谁不想做什么?)你需要做的是调用dbContext.Products.Remove(产品)以确保它被删除。这基本上意味着您的业务逻辑代码需要了解dbContext

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

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

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

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

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

答案 2 :(得分:0)

a)你首先要做的是什么?您是否不能将该集合设为私有,并且只公开对其进行快照的公共属性?

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

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

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

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

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

答案 3 :(得分:0)

如果其他人正在努力解决这个问题,这是我能想到的最好的实现,看看RemoveFromBasket,AddToBasket方法,不理想,但至少你得到的东西&amp;正在运行

 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<Basket, Int32>
{
    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<Basket> 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

}

}

答案 4 :(得分:0)

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

public partial class Basket
{
    public Basket()
    {
        this.BasketProducts = new List<BasketProduct>();
    }

    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<BasketProduct> 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<Basket, Int32>
{
    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<Basket> GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection)
    {
        throw new NotImplementedException();
    }

    public void SaveForUnitOfWork()
    {
        dbContext.SaveChanges();
    }

}