在 EF 定义架构的情况下使用 Code-First 时,EF 将使用约定在表中创建合适的 FK 列并在幕后使用这些列。您可以使用所需的任何命名约定定义自己的 FK,但您需要告诉 EF 使用哪一列作为哪个表的 FK 关系。这是通过使用[ForeignKey] 属性或使用外键配置关系(modelBuilder 或EntityTypeConfiguration)来完成的。
按照约定,EF 将通过相关对象的Type 定义FK 列名,这可能不是您在架构中想要的。例如,如果您有一个 Order 实体,您希望引用 CreatedBy 和 LastModifiedBy 的 User 实体,您可能希望有类似
public class Order
{
// ...
public virtual User CreatedBy { get; set; }
public virtual User LastModifiedBy { get; set; }
}
EF 会将 FK 创建为:User_Id 和 User_Id2
这可能会使想要在实体中包含 FK 的人绊倒:
public class Order
{
// ...
public int CreatedByUserId { get; set; }
public virtual User CreatedBy { get; set; }
public int LastModifiedByUserId { get; set; }
public virtual User LastModifiedBy { get; set; }
}
...期待它能够工作,然后想知道为什么没有填充表中的 ID。
要使用更有意义的 FK,您需要配置它们:
public class Order
{
// ...
[ForeignKey("CreatedBy")]
public int CreatedByUserId { get; set; }
public virtual User CreatedBy { get; set; }
[ForeignKey("LastModifiedBy")]
public int LastModifiedByUserId { get; set; }
public virtual User LastModifiedBy { get; set; }
}
ForeignKey 属性可以放在指向导航属性的 FK 上,也可以放在指向 FK 的导航属性上。
但是,我通常建议不要在实体中定义 FK,因为这可能会在更新引用时导致潜在的问题,因为关系有 2 个真实来源。
我使用order.LastModifiedByUserId 还是order.LastModifiedBy.UserId?如果我想更新用户并且某些代码引用其中一个或另一个怎么办?如果我更改用户导航属性,FK on Order 何时更新?如果我设置的是 FK 而不是导航属性呢?
如果我想更新订单的LastModifiedBy:
我可能很想这样做:
var order = context.Orders.Single(x => x.OrderId == orderId);
order.LastModifiedByUserId = currentUserId;
但是,我应该这样做:
var currentUser = context.Users.Single(x => x.UserId == currentUserId);
var order = context.Orders.Single(x => x.OrderId == orderId);
order.LastModifiedBy = currentUser;
取决于 LastModifiedBy 导航属性是预先加载的,还是 DBContext 可以填充它(因为它已经加载并且可以在订单实体饱和时填充)将决定尝试更新 FK 的行为。第二个选项是一致的,但是请注意,即使在那里,在调用 SaveChanges 之前,订单上的任何 FK 都不会自动更新。
通常最好避免在实体中暴露 FK。对于 EF6,这可以通过使用 Map.MapKey 来完成,其中 EF Core 支持影子属性。例如,配置以下实体来定义要使用的外键列名称,但避免暴露外键属性:
public class Order
{
// ...
public virtual User CreatedBy { get; set; }
public virtual User LastModifiedBy { get; set; }
}
使用模型构建器或EntityTypeConfiguration
EF6
.HasRequired(x => x.CreatedBy)
.WithMany()
.Map(x => x.MapKey("CreatedByUserId"));
.HasRequired(x => x.LastModifiedBy)
.WithMany()
.Map(x => x.MapKey("LastModifiedByUserId"));
EF 核心
.HasOne(x => x.CreatedBy)
.WithMany()
.HasForeignKey("CreatedByUserId");
.HasOne(x => x.LastModifiedBy)
.WithMany()
.HasForeignKey("LastModifiedByUserId");
/w EF Core HasForeignKey 可用于为 FK 列定义影子属性,而不是指向实体中的公开属性。这里的好处是相关实体的密钥不再有两个真相来源。