在数据库中,表与表之间的关系有一对一、一对多、多对多这三种,EFCore 也支持映射这些关系。

一对多

为了便于描述,在一对多关系中,遵循英语的表达,将一的那端称作 Principal Entity,多的那端称作 Dependent Entity。通常来说,Principal 在数据表中有一个主键,而 Dependent 在数据表中有一个外键,外键引用 Principal 的主键。而在实体模型中,则表示为 Principal 拥有一个 Dependent 类型的集合引用,而 Dependent 拥有一个 Principal 类型的单体引用。这两个引用,称为实体关系的导航属性(Navigation Property)

以 Blog 和 Post 为例,在实体类中表示一对多关系:

class Blog
{
    public long Id { get; set; }
    public string Name { get; set; }
    public List<Post> Posts { get; set; }
}

class Post
{
    public long Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public long BlogId { get; set; }
    public Blog Blog { get; set; }
}

如代码所示,Blog 有一个 Post 类型的集合引用,而 Post 有一个 Blog 类型的单体引用,以及一个名为 BlogId 的外键指向 Blog 的 主键 Id。

虽然这里并没有任何 C# 代码指明 Blog 和 Post 的关系,但是 EFCore 能够智能检测出来,即当 EFCore 在两个类型中都能找到对方的引用时,就会为它们配置实体关系,并且会将 Dependent 里名字匹配的属性视作外键。什么样的名字匹配呢?有下面四种:

  • <navigation property name><principal key property name>
  • <navigation property name>Id
  • <principal entity name><principal key property name>
  • <principal entity name>Id

故上面代码中,BlogId 会被视为外键。为了明确实体之间的关系,我们通常会(也推荐)选择自己在实体配置中告诉 EFCore 这两个实体的关系,这通过 EntityTypeBuilder 的 HasOne/HasMany/WithOne/WithMany 方法来完成。

HasOne/HasMany 用于指明实体关系下当前实体的导航属性,HasOne 会返回一个 ReferenceNavigationBuilder 而 HasMany 会返回一个 CollectionNavigationBuilder,它们都有 WithOne 和 WithMany 方法,用于指明实体关系下对方实体的导航属性。

比如在 Blog 的实体配置类这么写,就表明 Blog 和 Post 是一对多关系。

public void Configure(EntityTypeBuilder<Blog> builder)
{
    builder.HasMany<Post>(e => e.Posts).WithOne(e => e.Blog);
    builder.ToTable("T_Blogs");
   
}

这里 HasMany 需要传入泛型,因为要告诉 EFCore,Blog 实体是哪一个实体建立关系,并且在参数里写明 Blog 的导航属性,即第一个委托里的 e 是 Blog 类型的,导航属性是 e.Posts。

而 WithOne 不需要传入泛型,因为此时 EFCore 已经知道关系的双方是 Blog 和 Post,而这里需要确定 Blog 的对方即 Post 的导航属性,所以第二个委托里的 e 是 Post 类型的,导航属性是 e.Blog。

到这里关系的配置就已经完成了,在 Post 里不需要再进行多余的配置。当然你也可以选择只在 Post 的实体类里配置:

public void Configure(EntityTypeBuilder<Post> builder)
{
    builder.HasOne<Blog>(e => e.Blog).WithMany(e => e.Posts);
    builder.ToTable("T_Posts");
}

此时 EFCore 依旧会按照上面所说的名字规则去判断外键,最好是手动指定:

builder.HasMany<Post>(e => e.Posts).WithOne(e => e.Blog).HasForeignKey(e => e.BlogId);

无论是在 Blog 还是在 Post 里配置外键都是可以的,HasForeignKey 方法中的 e 一定是 Dependent 所属类型。