1

在花了几天时间尝试使用 EF 和 DDD 设置一个简单的应用程序之后,我不得不说我感到非常沮丧,并认为我最好还是使用 Linq-to-SQL 并忘记所有关于 DDD 和 EF 的事情。

英孚

a) 你不能有正确的只读集合

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

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

鉴于我发现的变通方法看起来很讨厌,所有这些对我来说几乎都是阻碍。是否有人设法组建了一个简单的存储库来解决这些问题?

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

另外,我知道这是一个大话题,是否有人亲身体验过大型 Web 应用程序中的任何 DDD 优势?我们都知道这个理论,但如果它真的值得麻烦,那么有一个想法会很好!


好的,到目前为止,我可以做的最好的事情是在查询某些内容时使用 AsNoTracking() ,而不必做各种疯狂的解决方法。这样我就可以得到我的信息,而 EF 就不用管它在我背后做的任何事情。我现在可以从集合中删除,并且我也可以删除(谁会认为 id 必须为此返回 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

}

}

4

5 回答 5

1

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

对 c 的回应:更一般的感觉,但删除所有子项并重新插入听起来很容易出错并且有“气味”。至少只有在绝对需要时我才会这样做。从性能的角度来看,更新可能更快。也许您可以重新思考为什么选择删除并重新插入的问题?

于 2012-12-03T19:32:37.190 回答
1

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

a)这是可能的,但由于这是第 5 版,我希望会有更好的结果。可能最简单的解决方法可以在这里找到 http://edo-van-asseldonk.blogspot.co.uk/2012/03/readonly-collections-with-entity.html 或者我想你甚至可以想出你自己的特定于当前问题的只读集合,例如 BasketProductsReadOnlyCollection 如果您有一个购物篮及其产品的集合。

b) 无论如何,我们可能不必担心 a。鉴于这里的问题,微软在“天才之举”中几乎不可能编写正确的 DDD 代码。如果您的 Products 表中有一个 Basket 和 Products 的 BasketId 不可为空,那么如果您执行 Basket.RemoveProduct(product),您就会遇到麻烦。删除这样的东西意味着删除“关系”而不是记录。所以 EF 会尝试将 BasketId 设置为 null,如果它不能设置它会抛出异常(不,我不想让它为 null 只是为了适应 EF,即使我想要如果我与一个没有的 DBA 一起工作怎么办?)你是什么需要做的是调用 dbContext.Products.Remove(product) 以确保它被删除。这基本上意味着您的业务逻辑代码需要了解 dbContext

c) 我不能再被打扰了!在 StackOverflow 上再次有关于此的响应,您可能会启动并运行某些东西,但它不应该那么困难和反直觉。

至于更大的图景,我查看了与“分离”实体一起使用的 N 层建议。我读了 Julia Lerman 的一本书,她似乎是这方面的权威,但我没有留下深刻的印象。整个附加对象图的工作方式和推荐的处理方式再次非常违反直觉。她推荐的让事情变得“简单”的方法是让每个对象在您的业务代码中记录其状态!不适合我。

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

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

底线:我想我会在这方面再给 MS 几年时间,他们最终可能会做对,但这还不适合我。MS 最终会在他们的网站上放置一些适当的文档/教程吗?我记得几年前在 NHibernate 上阅读了 30 万页的 PDF 教程。

于 2012-12-04T21:32:44.360 回答
0

万一其他人为此苦苦挣扎,这是我能想到的最佳实现,请查看 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<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

}

}

于 2012-12-06T09:48:39.903 回答
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();
    }

}

于 2012-12-06T12:16:48.980 回答
0

a) 你一开始想做什么?您不能将集合设为私有并仅公开对其进行快照的公共财产吗?

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

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

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

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

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

于 2012-12-04T13:18:44.937 回答