EF Code First:如何将虚拟集合设为私有,同时仍然让它正确创建我的数据库模型?

Posted

技术标签:

【中文标题】EF Code First:如何将虚拟集合设为私有,同时仍然让它正确创建我的数据库模型?【英文标题】:EF Code First: How do I make a virtual collection private while still having it correctly create my database model? 【发布时间】:2011-02-14 12:15:39 【问题描述】:

我正在使用 Code First 自动生成我的数据库,并且效果很好,当我添加一些测试数据时,会按预期生成一个 Orders 表和一个 OrderLines 表。

我有以下Order 类:

public class Order

    public int OrderID  get; set; 

    public void AddItem(string productCode, int quantity)
    
        var existingLine = OrderLines.FirstOrDefault(x => x.ProductOption.ProductCode == item.ProductCode);

        if (existingLine == null)
            OrderLines.Add(new OrderLine  ProductOption = item, Quantity = quantity );
        else
            existingLine.Quantity += quantity;
    

    public void RemoveItem(string productCode)
    
        OrderLines.Remove(OrderLines.Where(x => x.ProductOption.ProductCode == productCode).FirstOrDefault());
    

    public virtual ICollection<OrderLine> OrderLines  get; set; 

    public Order()
    
        OrderLines = new List<OrderLine>();
    

我真正想要的是封装 OrderLines 集合,使该类的消费者无法直接向其中添加和删除项目(使用 ICollection 的 Add / Remove 方法),而是强迫他们使用我自定义的AddItemRemoveItem 方法。

通常我可以将集合设为私有,但我不能这样做,因为它需要是虚拟的,EF 才能正确创建 OrderLines 表/外键。

This answer 似乎暗示创建属性internal 可以解决问题,但我尝试了,在这种情况下没有创建OrderLines 表。

有什么方法可以实现,或者我应该以某种不同的方式设计它?非常感谢任何帮助!

更新

经过一番搜索,我找到了this question,它比我的表述更清楚;但是,它仍然没有答案。海报确实链接到this post,这似乎表明它不能以我想的方式完成,但有人有更多最新信息吗?

【问题讨论】:

【参考方案1】:

我不知道是否可以按照您的要求进行操作,但我不确定这是不是最好的设计。我看到的问题是您正在将您的业务逻辑牢固地集成到您的业务实体中,我认为这将在未来变得混乱。

考虑以下情况。假设您有一个新要求,您希望用户能够从订单中删除所有项目。对您的实体执行此操作的唯一方法是为您的 Order 类创建一个新的 RemoveAllItems() 方法来执行此操作。现在假设您有一个新要求,即从特定类别的订单中删除所有商品。这意味着您必须添加另一种方法。

这会导致类非常臃肿,并且您会遇到一个主要问题。如果您(或其他开发人员)想要查看一个实体并确定它的数据结构,您无法一目了然,因为它与业务逻辑如此交织在一起。

我的建议是您将实体保留为纯数据结构,并公开它们的所有关系。然后您需要创建一个服务层,它可以由实际执行业务功能的小类或大类(无论您想如何组织它们)组成。例如,您可以有一个OrderItemService 类,它具有从订单中添加、编辑和删除项目的方法。你所有的业务逻辑都在这个类中执行,你只需要强制只允许服务类与数据库实体交互。

现在,如果您正在寻找特定的业务流程是如何执行的,您知道要查看服务层类,如果您想查看数据结构或实体的组织方式,您可以查看实体。这样可以保持一切清洁且非常易于维护。

【讨论】:

谢谢你,这似乎是一个明智的想法,事实上我一直在考虑实现一个服务层。我想我只是想把所有与Orders 相关的东西都放在一个地方。 尽管这不能直接回答问题,但我接受你的回答,因为这是我采用的解决方案——除此之外,我找不到任何这样做的方法'不觉得有点像黑客。 如果您要使用 DDD,那么作为纯数据结构的实体是一种称为贫血域的反模式。小心... 业务实体与业务无关,除非它们包含业务逻辑。您最终会得到无意义的数据传输对象并失去很多 OO 能力。我不同意接受的答案。 我不同意这个答案(如果对象变得过于“臃肿”/做了太多事情,那么你需要把它分开),但在某种程度上同意这个例子。 RemoveAllItems() 不是 Order 的责任。不过,将其移至名为OrderItemService 的位置也是错误的。它确实属于repository-pattern 类(可能作为IDbSet&lt;Order&gt; 的扩展方法,因为那是EF 的存储库实现)。【参考方案2】:

我远不是代码方面的专家,我还没有尝试过以下方法,但是否可以使用 ReadOnlyCollectionBase 并创建类似于this MSDN 文章的只读列表?

【讨论】:

我认为目前没有多少人是这方面的专家!重点不是我不能将集合设为只读,而是如果我这样做了,EF 就不会创建代表其子项的表。【参考方案3】:

你可以做的就是将你的集合设置为私有,并在 OnModelCreating 中使用 fluent API 建立关系,如下所示,我不知道这是否可行,请尝试一下:

public class YourContext : DbContext

   public DbSet<Order> Orders  get; set; 
   public DbSet<OrderLine> OrderLines  get; set; 

   protected override void OnModelCreating(ModelBuilder modelBuilder)
   
       modelBuilder.Entity<Order>() 
           .HasMany(o => o.OrderLines) 
           .WithRequired(l => l.OrderId) 
           .HasForeignKey(l => l.OrderId);

   

这将使您的 OrderLines 为只读:

public class YourContext : DbContext

   public DbSet<Order> Orders  get; set; 

   public DbSet<OrderLine> OrderLines 
    
      get  return set<OrderLine>(); 
   

希望对你有帮助,请看这篇博文:EF Feature CTP5: Fluent API Samples

【讨论】:

以上是关于EF Code First:如何将虚拟集合设为私有,同时仍然让它正确创建我的数据库模型?的主要内容,如果未能解决你的问题,请参考以下文章

EF 4.1 中使用 Code First 的 ComplexType 集合属性

EF6 Code First Lazy Load导致空集合

如何将时态表添加到 EF Code-First DBContext?

EF 7 Code First

如何在连接字符串中为 EF Code First 配置 ProviderManifestToken

如何在EF6 Code First中创建与枚举对应的表?