c# - Entity Framework 数据库表关联

标签 c# .net entity-framework datatables

我有一个像这样的域模型:

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; }
}

Entity Framework 将其与其他两个表(CustomersMovies)关联起来,并在 Rentals 表中自动创建外键列。在我的 .NET 类(class)中,我被教导如何通过添加 Id 属性来与其他表建立关联,因此例如 Rental 域模型将包含以下属性:

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

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

最佳答案

当使用 Code-First(其中 EF 定义架构)时,EF 将使用约定在表中创建合适的 FK 列并在幕后使用这些列。您可以使用所需的任何命名约定来定义自己的 FK,但您需要告诉 EF 哪个列用作哪个表的 FK 关系。这是通过使用 [ForeignKey] 属性或使用外键配置关系(modelBuilder 或 EntityTypeConfiguration)来完成的。

按照惯例,EF 将通过相关对象的类型来定义 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 何时会更新?如果我设置 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 之前,Order 上的任何 FK 都不会自动更新。

一般来说,最好完全避免暴露实体中的 FK。对于 EF6,这可以通过使用 Map.MapKey 来完成,其中 EF Core 支持 Shadow Properties。例如,配置以下实体来定义要使用的 FK 列名称,但避免公开 FK 属性:

public class Order
{
    // ...
    public virtual User CreatedBy { get; set; }
    public virtual User LastModifiedBy { get; set; }
}

使用 modelBuilder 或 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 列的影子属性,而不是指向实体中的公开属性。这样做的优点是相关实体的 key 不再有两个事实来源。

关于c# - Entity Framework 数据库表关联,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64560569/

相关文章:

c# - 如何获取 IMemoryCache 中 key 的 AbsoluteExpiration DateTimeOffset

entity-framework - EF 迁移问题

c# - 使用 Dynamic Linq 和 EPPlus 导出到 Excel?

c# - C# 中正确的可空类型检查?

c# - 如何创建泛型方法以返回调用指定的特定类型?

c# - 如何通过 C# 使用 Google 云数据存储

.net - 如何找到泛型参数的 TypeSpec

c# - 使用 JavaScript 在 GridView 中选择行

c# - 如何定义实体的填充方式?

c# - 反转位移运算符/2 的幂