Entity Framework Core——3.实体关系的配置

https://docs.microsoft.com/zh-cn/ef/core/modeling/relationships

1. 术语介绍

  • Dependent entity: 依赖实体(子实体),包含外键的实体,实体关系中的子。
  • Principal entity: 主体实体(父实体),包含主键/备用键的实体,实体关系中的父。
  • Principal key: 主键/备用键
  • Foreign key: 外键
  • Navigation property: 导航属性,出现在父实体或主实体中,用来维系两个实体间的关系。
    • Collection navigation property:集合导航,是一个ICollection<T>。不要配置为IList、List等类型,可能会有性能问题,参见这里
    • Reference navigation property:引用导航,是一个T
    • Inverse navigation property:反向导航属性
  • Self-referencing relationship:自引用的关系,如树形结构中的id和parentId

参考以下代码:

public class Blog//父实体
{
    public int BlogId { get; set; }//主键
    public string Url { get; set; }

    public ICollection<Post> Posts { get; set; }//导航属性,也是Post.Blog的反向导航属性
}

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; }//导航属性,也是Blog.Posts的反向导航属性
}

2. 关系自动配置

如果实体类的一个属性无法映射为标量类型(scalar type), 如int、long等,则认为它就是导航属性。
如果在某个实体类上发现了导航属性,将会创建一个关系。

2.1 完整配置

如上一小节的PostBlog的关系,定义比较完备。定义完备有两点要求:

  • 实体两边有对应的导航属性,而非一边有一边没有
  • 如果“子实体”中某个属性的名称符合以下规则(即外键的命名规则),则被配置为外键:
    • <navigation property name><principal key property name>
    • <navigation property name>Id
    • <principal entity name><principal key property name>
    • <principal entity name>Id

2.2 无外键情况下进行配置

虽然建议要有外键,但是没有外键也ok。当没外键时会引入名称符合<navigation property name><principal key property name> <principal entity name><principal key property name>规则的影子属性作为外键。

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    
    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    
    public Blog Blog { get; set; }
}

此时会产生一个名为BlogId的影子属性。注意:这个BlogId仍然会出现在数据库中,且是可空类型

2.3 无“导航属性对”情况下配置(单向导航)

即使没有对应的反向导航属性,没有外键属性,但只要存在一个导航属性就能建立起关系

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

3. 关系手动配置

使用HasOne/HasMany配置导航属性,使用WithOne/WithMany配置反向导航属性。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)//表示一个Post对应一个Blog
            .WithMany(b => b.Posts);//表示一个Blog对应多个Post
            
        //上述代码等效于下面这两行
        modelBuilder.Entity<Post>().HasOne(p=>p.Blog);
        modelBuilder.Entity<Blog>().HasMany(b=>b.Posts);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

当然也可以通过特性配置:

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int AuthorUserId { get; set; }
    public User Author { get; set; }

    public int ContributorUserId { get; set; }
    public User Contributor { get; set; }
}

public class User
{
    public string UserId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    [InverseProperty("Author")]
    public ICollection<Post> AuthoredPosts { get; set; }

    [InverseProperty("Contributor")]
    public ICollection<Post> ContributedToPosts { get; set; }
}

看下建表语句都是什么:

CREATE TABLE "SysRole" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_SysRole" PRIMARY KEY AUTOINCREMENT,
    "RoleName" TEXT NOT NULL
);

CREATE TABLE "SysUsers" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_SysUsers" PRIMARY KEY AUTOINCREMENT,
    "UserName" TEXT NOT NULL,
    "Pwd" TEXT NOT NULL,
    "CreateTime" TEXT NULL,
    "RoleId" INTEGER NULL,
    CONSTRAINT "FK_SysUsers_SysRole_RoleId" FOREIGN KEY ("RoleId") REFERENCES "SysRole" ("Id") ON DELETE RESTRICT
);

3.1 无“导航属性对”时手动配置(单向导航)

如果你的导航属性不是成对的,则WithOneWithMany可以不传入参数。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .HasMany(b => b.Posts)
            .WithOne();
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

同样:

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public Blog Blog {get; set; }
}

可配置为:
modelBuilder.Entity<Post>().HasOne(b => b.Blog).WithMany();

3.2 手动指定外键

使用FluentAPI可以配置单一外键和复合外键:
单一外键:

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey(p => p.BlogForeignKey);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogForeignKey { get; set; }
    public Blog Blog { get; set; }
}

复合外键:

internal class MyContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        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 });//复合外键
    }
}

public class Car
{
    public string State { get; set; }
    public string LicensePlate { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }

    public ICollection<RecordOfSale> SaleHistory { get; set; }
}

public class RecordOfSale
{
    public int RecordOfSaleId { get; set; }
    public DateTime DateSold { get; set; }
    public decimal Price { get; set; }

    public string CarState { get; set; }
    public string CarLicensePlate { get; set; }
    public Car Car { get; set; }
}

除了FluentAPI外,也可以使用特性配置单一外键:

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogForeignKey { get; set; }

    [ForeignKey("BlogForeignKey")]//如果BlogForeignKey不存在,则会创建影子属性
    public Blog Blog { get; set; }
}

配置影子外键:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
        //1.首先配置影子属性
        modelBuilder.Entity<Post>()
            .Property<int>("BlogForeignKey");

        //2.然后将影子属性配置成外键
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey("BlogForeignKey");
}

配置外键的名称:默认情况下外键约束的名称规则为FK _<依赖实体的类名>_<主体实体的类名>_<外键属性的名称>。在数据库中可以看到实际的名称,如FK_SysUsers_SysRole_RoleId
当然也可以手动更改外键约束的名称:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .HasForeignKey(p => p.BlogId)
        .HasConstraintName("ForeignKey_Post_Blog");
}

3.3 无导航属性时手动配置

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne<Blog>()
            .WithMany()
            .HasForeignKey(p => p.BlogId);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { 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; }
}

3.4 将外键指向到非主键上

如果希望外键指向到非实体主键的属性上。则可以如下配置:

  1. 单一外键
internal class MyContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<RecordOfSale>()
            .HasOne(s => s.Car)
            .WithMany(c => c.SaleHistory)
            .HasForeignKey(s => s.CarLicensePlate)
            .HasPrincipalKey(c => c.LicensePlate);
    }
}

public class Car
{
    public int CarId { get; set; }
    public string LicensePlate { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }

    public ICollection<RecordOfSale> SaleHistory { get; set; }
}

public class RecordOfSale
{
    public int RecordOfSaleId { get; set; }
    public DateTime DateSold { get; set; }
    public decimal Price { get; set; }

    public string CarLicensePlate { get; set; }
    public Car Car { get; set; }
}
  1. 复合外键
internal class MyContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<RecordOfSale>()
            .HasOne(s => s.Car)
            .WithMany(c => c.SaleHistory)
            .HasForeignKey(s => new { s.CarState, s.CarLicensePlate })//CarState, CarLicensePlate的顺序需要和State, LicensePlate对应
            .HasPrincipalKey(c => new { c.State, c.LicensePlate });
    }
}

public class Car
{
    public int CarId { get; set; }
    public string State { get; set; }
    public string LicensePlate { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }

    public ICollection<RecordOfSale> SaleHistory { get; set; }
}

public class RecordOfSale
{
    public int RecordOfSaleId { get; set; }
    public DateTime DateSold { get; set; }
    public decimal Price { get; set; }

    public string CarState { get; set; }
    public string CarLicensePlate { get; set; }
    public Car Car { get; set; }
}

3.5 设置外键可否为空

默认情况下外键可否为空是根据实体类中外键属性可否为空来的。

你可以通过 以下方式配置影子外键可否为空:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>().HasOne(p => p.Blog).WithMany(b => b.Posts).IsRequired();
}

3.6 设置级联删除

父实体删除时删除对应的子实体

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .OnDelete(DeleteBehavior.Cascade);
}

4. 其他关系的配置

上面的例子我们介绍的都是一对多的关系,接下来介绍其他类型的关系

4.1 一对一

两侧的导航属性都不是集合,在外键属性上有唯一索引。EF会自动区分子实体和父实体。

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogImage BlogImage { get; set; }
}

public class BlogImage
{
    public int BlogImageId { get; set; }
    public byte[] Image { get; set; }
    public string Caption { get; set; }
    //定义了外键,EF认为这个就是子实体
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

如果EF自动区分时出错,则可以根据之前介绍的使用FluentAPI和特性手动指定。

4.2 多对多

两侧的导航属性都是集合。

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public ICollection<Post> Posts { get; set; }
}

会额外创建一个关联表:

CREATE TABLE [Posts] (
    [PostId] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NULL,
    [Content] nvarchar(max) NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([PostId])
);

CREATE TABLE [Tags] (
    [TagId] nvarchar(450) NOT NULL,
    CONSTRAINT [PK_Tags] PRIMARY KEY ([TagId])
);

CREATE TABLE [PostTag] (
    [PostsId] int NOT NULL,
    [TagsId] nvarchar(450) NOT NULL,
    CONSTRAINT [PK_PostTag] PRIMARY KEY ([PostsId], [TagsId]),//复合主键
    CONSTRAINT [FK_PostTag_Posts_PostsId] FOREIGN KEY ([PostsId]) REFERENCES [Posts] ([PostId]) ON DELETE CASCADE,
    CONSTRAINT [FK_PostTag_Tags_TagsId] FOREIGN KEY ([TagsId]) REFERENCES [Tags] ([TagId]) ON DELETE CASCADE
);

当然也可以显示的创建一个PostTag实体来定义多对多的关系:

public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options)
        : base(options)
    {
    }

    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<PostTag>()
            .HasKey(t => new { t.PostId, t.TagId });

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Post)
            .WithMany(p => p.PostTags)
            .HasForeignKey(pt => pt.PostId);

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Tag)
            .WithMany(t => t.PostTags)
            .HasForeignKey(pt => pt.TagId);
    }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public ICollection<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public ICollection<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public DateTime PublicationDate { get; set; }

    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JimCarter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值