EFCore 一对一 实体关系
一对一 实体关系
当一个实体与最多一个其他实体关联时,将使用一对一关系。 例如,Blog
有一个 BlogHeader
,并且 BlogHeader
属于单个 Blog
。
必需的一对一
// Principal (parent) public class Blog { public int Id { get; set; } public BlogHeader? Header { get; set; } // Reference navigation to dependent } // Dependent (child) public class BlogHeader { public int Id { get; set; } public int BlogId { get; set; } // Required foreign key property public Blog Blog { get; set; } = null!; // Required reference navigation to principal }
一对一关系由以下部分组成:
- 主体实体上的一个或多个主键或备用键属性。 例如
Blog.Id
。 - 依赖实体上的一个或多个外键属性。 例如
BlogHeader.BlogId
。 - (可选)引用依赖实体的主体实体上的引用导航。 例如
Blog.Header
。 - (可选)引用主体实体的依赖实体上的引用导航。 例如,
BlogHeader.Blog
。
提示
一对一关系的哪一端应是主体实体,哪一端应是依赖实体,这并不总是显而易见的。 请注意以下事项:
- 如果两种类型的数据库表已存在,则具有外键列的表必须映射到依赖类型。
- 如果某个类型在逻辑上不能在没有另一个类型的情况下存在,则它通常是依赖类型。 例如,对于不存在的博客,它的标题是没有意义的,因此
BlogHeader
自然是依赖类型。 - 如果存在自然的父/子关系,则子级通常是依赖类型。
因此,对于此示例中的关系:
- 外键属性
BlogHeader.BlogId
不可为空。 这会使关系成为“必需”关系,因为每个依赖实体 (BlogHeader
) 必须与某个主体实体 (Blog
) 相关,而其外键属性必须设置为某个值。 - 这两个实体都有指向关系另一端的相关实体的导航。
提示
具有两个导航的关系(一个是从依赖实体到主体实体,一个是从主体实体到依赖实体)称为双向关系。
此关系按约定发现。 即:
Blog
作为关系中的主体实体被发现,BlogHeader
作为依赖实体被发现。BlogHeader.BlogId
作为引用主体实体的Blog.Id
主键的依赖实体的外键被发现。 由于BlogHeader.BlogId
不可为空,所以发现这一关系是必需的。Blog.BlogHeader
作为引用导航被发现。BlogHeader.Blog
作为引用导航被发现。
重要
使用 C# 可为空引用类型时,如果外键属性可为空,则从依赖实体到主体实体的导航必须可为空。 如果外键属性不可为空,则导航可以为空,也可以不为空。 在这种情况下,BlogHeader.BlogId
和 BlogHeader.Blog
皆不可为空。 = null!;
构造用于将此标记为 C# 编译器的有意行为,因为 EF 通常会对 Blog
实例进行设置,并且对于完全加载的关系,它不能为空。 有关详细信息,请参阅使用可为空引用类型。
对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasOne(e => e.Header) .WithOne(e => e.Blog) .HasForeignKey<BlogHeader>(e => e.BlogId) .IsRequired(); }
在上面的示例中,关系的配置将启动主体实体类型 (Blog
)。 与所有关系一样,它完全等效于从依赖实体类型 (BlogHeader
) 开始。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<BlogHeader>() .HasOne(e => e.Blog) .WithOne(e => e.Header) .HasForeignKey<BlogHeader>(e => e.BlogId) .IsRequired(); }
与另一个选项相比,这两个选项并没有什么优势:它们都会导致完全相同的配置。
提示
没有必要对关系进行两次配置,即先从主体实体开始,又从依赖实体开始。 此外,尝试单独配置关系的主体实体和依赖实体通常不起作用。 选择从一端或另一端配置每个关系,然后只编写一次配置代码。
可选的一对一
// Principal (parent) public class Blog { public int Id { get; set; } public BlogHeader? Header { get; set; } // Reference navigation to dependent } // Dependent (child) public class BlogHeader { public int Id { get; set; } public int? BlogId { get; set; } // Optional foreign key property public Blog? Blog { get; set; } // Optional reference navigation to principal }
这与上一个示例相同,只不过外键属性和到主体实体的导航现在可为空。 这会使关系成为“可选”关系,因为依赖实体 (BlogHeader
) 不能通过将其外键属性和导航设置为 null
来与任何主体 (Blog
) 相关。
重要
使用 C# 可为空引用类型时,如果外键属性可为空,则从依赖实体到主体实体的导航属性必须可为空。
如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasOne(e => e.Header) .WithOne(e => e.Blog) .HasForeignKey<BlogHeader>(e => e.BlogId) .IsRequired(false); }
具有主键到主键关系的必需的一对一
// Principal (parent) public class Blog { public int Id { get; set; } public BlogHeader? Header { get; set; } // Reference navigation to dependent } // Dependent (child) public class BlogHeader { public int Id { get; set; } public Blog Blog { get; set; } = null!; // Required reference navigation to principal }
与一对多关系不同,一对一关系的依赖端可以将其主键属性用作外键属性。 这通常称为 PK 到 PK 关系。 仅当主体类型和依赖类型具有相同的主键类型,并且始终需要生成的关系时才有可能,因为依赖类型的主键不可为空。
必须配置未按约定发现外键的任何一对一关系,以指示关系的主体端和依赖端。 这通常是使用对 HasForeignKey
的调用来完成的。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasOne(e => e.Header) .WithOne(e => e.Blog) .HasForeignKey<BlogHeader>(); }
如果对 HasForeignKey
的调用中未指定任何属性,并且主键适用,则将其用作外键。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasOne(e => e.Header) .WithOne(e => e.Blog) .HasForeignKey<BlogHeader>(e => e.Id) .IsRequired(); }
具有备用键的一对一
在到目前为止的所有示例中,依赖实体上的外键属性被约束为主体实体上的主键属性。 外键可以改为被约束为不同的属性,该属性随后成为主体实体类型的备用键。 例如:
// Principal (parent) public class Blog { public int Id { get; set; } public int AlternateId { get; set; } // Alternate key as target of the BlogHeader.BlogId foreign key public BlogHeader? Header { get; set; } // Reference navigation to dependent } // Dependent (child) public class BlogHeader { public int Id { get; set; } public int BlogId { get; set; } // Required foreign key property public Blog Blog { get; set; } = null!; // Required reference navigation to principal }
此关系不是按约定发现的,因为 EF 始终按照约定创建与主键的关系。 可以使用对 HasPrincipalKey
的调用在 OnModelCreating
中显式配置它。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasOne(e => e.Header) .WithOne(e => e.Blog) .HasPrincipalKey<Blog>(e => e.AlternateId); }
HasPrincipalKey
可与其他调用结合使用,以显式配置导航、外键属性和必需/可选性质。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasOne(e => e.Header) .WithOne(e => e.Blog) .HasPrincipalKey<Blog>(e => e.AlternateId) .HasForeignKey<BlogHeader>(e => e.BlogId) .IsRequired(); }
自引用一对一
在前面的所有示例中,主体实体类型与依赖实体类型有所不同。 情况不一定如此。 例如,在下面的类型中,每个 Person
类型都可选择与另一个 Person
相关。
public class Person { public int Id { get; set; } public int? HusbandId { get; set; } // Optional foreign key property public Person? Husband { get; set; } // Optional reference navigation to principal public Person? Wife { get; set; } // Reference navigation to dependent }
此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Person>() .HasOne(e => e.Husband) .WithOne(e => e.Wife) .HasForeignKey<Person>(e => e.HusbandId) .IsRequired(false); }
备注
对于一对一自引用关系,由于主体实体类型和依赖实体类型相同,指定包含外键的类型不会阐明依赖端。 在本例中,从依赖实体到主体实体以 HasOne
点为单位指定的导航,以及从主体实体到依赖实体以 WithOne
点为单位指定的导航。
参考:https://learn.microsoft.com/zh-cn/ef/core/modeling/relationships/one-to-one