【问题标题】:Code First: Independent associations vs. Foreign key associations?代码优先:独立关联与外键关联?
【发布时间】:2011-07-14 00:14:35
【问题描述】:

每当我开始从事一个新项目并设计我的 POCO 时,我都会与自己进行一场心理辩论。我看过许多似乎支持外键关联的教程/代码示例:

外键关联

public class Order
{
    public int ID { get; set; }
    public int CustomerID { get; set; } // <-- Customer ID
    ...
}

相对于独立的关联

独立关联

public class Order
{
    public int ID { get; set; }
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

我过去曾使用过 NHibernate,并使用独立关联,这不仅感觉更 OO,而且(通过延迟加载)具有让我访问整个 Customer 对象的优势,而不仅仅是它的 ID。例如,这使我可以检索一个 Order 实例,然后执行 Order.Customer.FirstName 而无需显式执行连接,这非常方便。

回顾一下,我的问题是:

  1. 在 使用独立协会?还有...
  2. 如果没有,是什么 会是使用外键关联的原因吗?

【问题讨论】:

    标签: entity-framework poco


    【解决方案1】:

    如果你想充分利用 ORM,你肯定会使用实体引用:

    public class Order
    {
        public int ID { get; set; }
        public Customer Customer { get; set; } // <-- Customer object
        ...
    }
    

    一旦您从具有 FK 的数据库生成实体模型,它将始终生成实体引用。如果您不想使用它们,则必须手动修改 EDMX 文件并添加表示 FK 的属性。至少在 Entity Framework v1 中是这种情况,只允许独立关联。

    实体框架 v4 提供了一种称为外键关联的新型关联。独立和外键关联最明显的区别在于 Order 类:

    public class Order
    {
        public int ID { get; set; }
        public int CustomerId { get; set; }  // <-- Customer ID
        public Customer Customer { get; set; } // <-- Customer object
        ...
    }
    

    如您所见,您同时拥有 FK 属性和实体引用。两种类型的关联有更多区别:

    独立关联

    • 它在ObjectStateManager 中表示为单独的对象。它有自己的EntityState
    • 在建立关联时,您总是需要来自关联两端的实体
    • 此关联的映射方式与实体相同。

    外键关联

    • 它在ObjectStateManager 中不表示为单独的对象。因此,您必须遵守一些特殊规则。
    • 在建立关联时,您不需要关联的两端。有子实体和父实体的 PK 就足够了,但 PK 值必须是唯一的。因此,在使用外键关联时,您还必须为关系中使用的新生成实体分配临时唯一 ID。
    • 此关联未映射,而是定义了引用约束。

    如果您想使用外键关联,您必须在实体数据模型向导中勾选在模型中包含外键列

    编辑:

    我发现这两种类型的关联之间的区别不是很清楚,所以I wrote a short article 对此进行了详细介绍以及我对此的看法。

    【讨论】:

    • 感谢您非常有见地的回答,以及对正确术语的提醒,这帮助我找到了有关该主题的大量资源以及两种技术的优缺点。
    • 我刚看到你的文章,Ladislav。非常有趣的阅读,以及进一步了解这两种方法之间差异的重要资源。干杯。
    • @GaussZ:据我所知,自 EF4(引入 FK 关联)以来,关联的处理方式没有任何变化。
    • 这个和其他答案似乎没有触及性能问题。但是,根据 MSDN 文章中的2.2 Factors that affect View Generation performance 部分,使用独立关联似乎会增加视图生成的成本,而不是外键关联。
    • @LadislavMrnka:你能仔细检查一下你上面提到的文章的链接是否有效吗?我无法访问它。
    【解决方案2】:

    独立关联不适用于AddOrUpdate,通常用于Seed 方法。当引用是现有项目时,它将被重新插入。

    // Existing customer.
    var customer = new Customer { Id = 1, Name = "edit name" };
    db.Set<Customer>().AddOrUpdate(customer);
    
    // New order.
    var order = new Order { Id = 1, Customer = customer };
    db.Set<Order>().AddOrUpdate(order);
    

    结果是现有客户将被重新插入,新(重新插入的)客户将与新订单相关联。


    除非我们使用外键关联并分配 id。

     // Existing customer.
    var customer = new Customer { Id = 1, Name = "edit name" };
    db.Set<Customer>().AddOrUpdate(customer);
    
    // New order.
    var order = new Order { Id = 1, CustomerId = customer.Id };
    db.Set<Order>().AddOrUpdate(order);
    

    我们有预期的行为,现有客户将与新订单相关联。

    【讨论】:

    • 这是一个很好的发现。但是,将客户附加到订单的解决方法(我认为正确的方法)是从数据库上下文加载它,如下所示:var order = new Order { Id = 1, Customer = db.Customers.Find(1) }; 或者您可以使用 Select 方法从数据库上下文加载客户。这适用于独立关联。
    【解决方案3】:

    两者都用。并使您的实体引用虚拟以允许延迟加载。像这样:

    public class Order
    {
      public int ID { get; set; }
      public int CustomerID { get; set; }
      public virtual Customer Customer { get; set; } // <-- Customer object
      ...
    }
    

    这可以节省不必要的数据库查找,允许延迟加载,并且如果您知道自己想要什么,则可以轻松查看/设置 ID。请注意,两者都不会以任何方式改变您的表结构。

    【讨论】:

    • 同意。正如拉迪斯拉夫建议的那样,这就是我最终要做的。它真的给你两全其美;当你需要它的所有属性时是整个对象,当你只需要 PK 而不关心其余部分时它的 ID。
    • 来自2021年的文档:因为延迟加载非常容易无意中触发N+1问题,建议避免。当数据库往返发生时,急切或显式加载在源代码中非常清楚。 https://docs.microsoft.com/en-us/ef/core/performance/efficient-querying#beware-of-lazy-loading
    【解决方案4】:

    我倾向于使用对象方法来避免不必要的查找。当您调用工厂方法来构建整个实体(使用嵌套实体的简单回调代码)时,属性对象可以很容易地填充。除了内存使用之外,我看不到任何缺点(但你会缓存你的对象吗?)。因此,您所做的就是用堆栈代替堆,并通过不执行查找来提高性能。我希望这是有道理的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-31
      • 1970-01-01
      • 2015-09-03
      • 2012-09-18
      • 2012-12-06
      • 1970-01-01
      • 2018-05-31
      相关资源
      最近更新 更多