代码优先:独立协会与外键协会?


102

每当我开始从事一个新项目并设计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合作,并使用了独立的关联,这不仅使我感觉更多面向对象,而且(带有延迟加载)还具有使我可以访问整个Customer对象而不只是ID的优点。例如,这使我可以检索Order实例,然后执行操作Order.Customer.FirstName而不必显式进行联接,这非常方便。

回顾一下,我的问题是:

  1. 使用独立关联是否有任何重大缺点?和...
  2. 如果没有,根本不使用外键关联的原因是什么?

Answers:


106

如果要充分利用ORM,则一定要使用Entity引用:

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。
  • 该关联未映射,而是定义了引用约束。

如果要使用外键关联,则必须在“实体数据模型向导” 中的模型中选中“ 包括外键列”

编辑:

我发现这两种类型的关联之间的区别不是很清楚,所以我写了一篇简短的文章,其中涵盖了更多细节和我对此的看法。


感谢您非常有见地的回答,以及有关正确术语的提示,它帮助我找到了关于该主题的大量资源以及这两种技术的优缺点。
Daniel Liuzzi

1
我刚看过你的文章,拉迪斯拉夫。非常有趣的阅读,以及大量的资源,可以进一步了解这两种方法之间的区别。干杯。
Daniel Liuzzi

1
@GaussZ:据我所知,自从EF4(引入FK关联)以来,关联处理方式没有任何变化。
Ladislav Mrnka

1
这个和其他答案似乎并没有涉及性能问题。但是,根据第2.2节MSDN文章中影响视图生成性能的因素,使用独立关联似乎比外键关联增加了视图生成的成本。
Veverke '16

1
@LadislavMrnka:您能仔细检查一下您上面提到的动脉的链接吗?我无法访问它。
Veverke '16

34

同时使用。并使您的实体引用为虚拟,以允许延迟加载。像这样:

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

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


5
同意 正如Ladislav所建议的,这就是我最终要做的。它确实为您提供了两全其美的优势;需要对象的所有属性时使用整个对象,而只需要PK而不关心其余对象时使用它的ID。
Daniel Liuzzi

9

独立关联不能很好地与方法中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);

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


2
这是一个好发现。但是,将客户附加到订单的解决方法(我认为是正确的方法)是从db上下文中加载它,如下所示:var order = new Order { Id = 1, Customer = db.Customers.Find(1) }; 或者,您可以使用Select方法从db上下文中加载客户。这与独立协会一起工作。
tala9999 '16

4

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.