【问题标题】:Entity Framework database tables association实体框架数据库表关联
【发布时间】:2020-10-27 18:16:10
【问题描述】:

我有一个这样的域模型:

public class Rental
{
    public int Id { get; set; }

    public DateTime DateRented { get; set; }
    public DateTime? DateReturned { get; set; }

    [Required]
    public Customer Customer { get; set; }

    [Required]
    public Movie Movie { get; set; }
}

实体框架将其与另外两个表(Customers 和 Movies)相关联,并在 Rentals 表中自动创建外键列。在我的 .NET 课程中,我被教导通过添加 Id 属性与其他表建立关联,因此例如 Rental 域模型将包含以下属性:

public int MovieId { get; set; }
public int CustomerId { get; set; }

但是没有这些代码行,代码运行良好。你能解释一下哪种方法更好吗?或者也许其中之一提供了更大的灵活性?我是 .NET 的新手,因此欢迎您的每一个解释。

【问题讨论】:

  • 您需要这些密钥来读取、更新或删除单个电影或客户。
  • 你会这么好心并扩展声明吗?

标签: c# .net entity-framework datatables


【解决方案1】:

在 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 列定义影子属性,而不是指向实体中的公开属性。这里的好处是相关实体的密钥不再有两个真相来源。

【讨论】:

    【解决方案2】:

    下面的第一行会抛出一个语法错误,这不是有效的 c# 代码

    public MovieId { get; set; }
    public CustomerId { get; set; }
    

    第二件事 EF 足够聪明,可以知道实体之间的关系,并且从您的用例角度来看,当您想要访问您拥有的有关客户(例如名称)的信息时,最好以这种方式放置 Typed 属性使用rental.Customer.Name 确实可以使用它,您不必单独拉它。

    【讨论】:

    • 你是对的。我纠正了它。所以据我了解,最好只写类型属性(没有 Id 属性)?在任何情况下编写这些额外的 Id 属性是有意义的吗?
    • @BartoszChodyła 我想不出来,因为您总是可以使用 Customer.Id 访问它
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-16
    • 1970-01-01
    相关资源
    最近更新 更多