关系
提纲: 比较多
- 关系\导航属性\自引用关系
- 每一个关系都会在相关实体中建立一个外键
- FluentApi 的作用(什么情况只能使用 FluentAPI)
术语
- 主体实体: 这是包含主/备用键属性的实体。 有时称为关系的 "父项"。例如下面的 Blog
- 主体健: 唯一标识主体实体的属性。 这可能是主键或备用健。例如下面Blog.BlogId
- 相关实体(依赖实体): 这是包含外键属性的实体。 有时称为关系的 "子级"。例如下面的 Post 类
- 外键: 用于存储相关实体的主体键值的依赖实体中的属性。例如下面 Post.BlogId
- 导航属性: 在主体和/或从属实体上定义的属性,该属性引用相关实体。
- 集合导航属性: 一个导航属性,其中包含对多个子实体的引用。 例如 Blog.Posts
- 引用导航属性: 保存对单个子实体的引用的导航属性。 例如 Post.Blog
- 反向导航属性: 讨论特定导航属性时,此术语是指关系另一端的导航属性。
Post.Blog 是 Blog.Posts 的反向导航属性(反之亦然)
- 自引用关系: 依赖关系和主体实体类型相同的关系。
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
关系
默认情况下,当在某个类型上发现导航属性时,将创建一个关系。
如何判断某一个属性是导航属性?
如果属性指向的类型不能由当前的数据库提供程序映射为标量类型,则该属性视为一个导航属性。
- 完全定义关系
- 单个导航属性
完全定义关系
最常见的模式是在关系两端定义导航属性,在依赖实体类中定义外键。
- 如果在两个类型之间找到一对导航属性,则这些属性将配置为同一关系的反向导航属性。
- 如果依赖实体包含名称与下面其中一种模式相匹配的属性,则该属性将被配置为外键:
- <navigation property name><principal key property name>, 导航属性名称+主实体主键名称,例如 XBlogBlogId, XBlog 为导航属性名称
- <navigation property name>Id, 导航属性名称+Id,例如 XBlogId
- <principal entity name><principal key property name>, 主实体名称+主实体主键名称,例如 BlogBlogId
- <principal entity name>Id, 主实体名称+Id,例如 BlogId
如果找不到和上面所匹配的属性并且没有使用 FluentApi 定义外键字段时,则 EFCORE 将自动产生一个外键属性
属性名称为:
- <navigation property name><principal key property name> ( 导航属性名称+主表主键名称) 或
- <principal entity name><principal key property name> ( 主体类名称+主表主键名称)
单个导航属性
- 包含一个导航属性(无反向导航,没有外键属性) 或者
例如, Blog中有 public List<Post> Posts { get; set; } , 而 Post中没有任何指向 Blog 的属性
- 包含一个导航属性(无反向导航) 和一个外键属性。
例如, Post中有 public Blog Blog { get; set; } , 而 Blog 中没有任何指向 Post 的属性
如上所述,也将产生一个外键属性, 此外键属性为隐藏外键属性
FluentApi中的方法:
使用 FluentApi 一般情形下没有必要,因为 EFCore 会自动寻找关系,并在相关表中产生外键.
除非:
- 需要自己指定关联关系的属性名称 (如果存在此属性)
使用 BlogForeignKey 属性作为关联字段
[ForeignKey("BlogForeignKey")] public Blog Blog { get; set; } 或者
modelBuilder.Entity<Post>() .HasOne(p => p.Blog) .WithMany(b => b.Posts) .HasForeignKey(p => p.BlogForeignKey);
- 关联关系为一个影子外键, 即列在类中不存在,但是在表中存在
列 BlogForeignKey 在 Post 中没有对应的属性,
modelBuilder.Entity<Post>().Property<int>("BlogForeignKey"); modelBuilder.Entity<Post>().HasOne(p => p.Blog).WithMany(b => b.Posts).HasForeignKey("BlogForeignKey");
- 需要自己指定外键的约束名称
modelBuilder.Entity<Post>() .HasOne(p => p.Blog) .WithMany(b => b.Posts) .HasForeignKey(p => p.BlogId) .HasConstraintName("ForeignKey_Post_Blog");
- 关联到组合主键
modelBuilder.Entity<Car>().HasKey(c => new { c.State, c.LicensePlate }); modelBuilder.Entity<RecordOfSale>().HasOne(s => s.Car).WithMany(c => c.SaleHistory) .HasForeignKey(s => new { s.CarState, s.CarLicensePlate });
5 没有导航属性,也需要建立关系
Post 和 Blog 2个类相互间没有任何关联关系
modelBuilder.Entity<Post>() .HasOne<Blog>() .WithMany() .HasForeignKey(p => p.BlogId);
- 关联到非主键 ( 备用健 )
modelBuilder.Entity<RecordOfSale>() .HasOne(s => s.Car) .WithMany(c => c.SaleHistory) .HasForeignKey(s => s.CarLicensePlate) .HasPrincipalKey(c => c.LicensePlate);
- 一对一时,需要使用 FluentApi 定义主表
modelBuilder.Entity<Blog>() .HasOne(b => b.BlogImage) .WithOne(i => i.Blog) .HasForeignKey<BlogImage>(b => b.BlogForeignKey);
- 一个表存在对另一个表的多个关系
- HasOne,WithOne 用于引用导航属性
- HasMany,WithMany 用于集合导航属性
- HasOne/WithMany 定义了一对(一对多)反向导航属性
- HasMany/WithOne 定义了一对(一对多)反向导航属性
- HasOne/WithOne 定义了一对(一对一)反向导航属性
- 以上均返回 CollectionNavigationBuilder<TEntity, TRelatedEntity> (导航的构建类)
- WIthMay , WithOne 定义在类中 CollectionNavigationBuilder<TEntity, TRelatedEntity>, 返回如下的实例
ReferenceCollectionBuilder<Blog, Post> 实例 ( 一对反向导航的构建类 )
下面代码都定义一对反向导航关系,返回 ReferenceCollectionBuilder<Blog, Post> 实例
modelBuilder.Entity<Post>().HasOne(p => p.Blog).WithMany(b => b.Posts);
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(x => x.Blog);
下面代码定义了 2个单向导航属性
返回 ReferenceCollectionBuilder<Blog, Post> 实例
表示 Blog 中有 List<Post> posts 属性, 而 Post 中没有 指向 Blog 的 属性
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne();
表示 Post 中有 Blog blog 属性, 而 Blog 中没有 指向 Post 的 属性
modelBuilder.Entity<Post>().HasOne(b => b.Blog).WithMany();