【EF Core】配置关系


EF Core中实体之间关系的配置的套路:

HasXXX(…).WithXXX(…);

XXX可选值One、Many。

  • 一对多:HasOne(…).WithMany(…);
    也可以配置多对一:HasMany(…).WithOne(…);
  • 一对一:HasOne(…).WithOne (…);
  • 多对多:HasMany (…).WithMany(…);

一对多

双向导航属性

    public class Article
    {
        public long Id { get; set; }//主键
        public string Title { get; set; }//标题
        public string Content { get; set; }//内容
        public List Comments { get; set; } = new List(); //此文章的若干条评论
    }
    class ArticleConfig : IEntityTypeConfiguration
{ public void Configure(EntityTypeBuilder
builder) { builder.ToTable("T_Articles"); } } public class Comment { public long Id { get; set; } public Article Article { get; set; } public long TheArticleId { get; set; } public string Message { get; set; } } public class CommentConfig : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("T_Comments"); builder.HasOne
(c => c.Article).WithMany(a => a.Comments) .IsRequired().HasForeignKey(c => c.TheArticleId); } }

Comment表中的TheArticleId属性并不是必须的,生成Comment表时会自动生成外键。
但是不创建外键属性的话,如果需要获取外键列的值,就需要关联查询,效率低,所以建议添加
如果添加了外键属性,需要通过HasForeignKey(c => c.TheArticleId)声明一下它是外键属性

单向导航属性

对于主从结构的“一对多”表关系,一般是声明双向导航属性。
而对于其他的“一对多”表关系:如果表属于被很多表引用的基础表,则用单项导航属性。
如用户id是很多表的外键,如果用户实体中添加所有的导航属性,会非常多。这种情况应该用单向导航属性。

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    class UserConfig : IEntityTypeConfiguration
    {
        public void Configure(EntityTypeBuilder builder)
        {
            builder.ToTable("T_Users");
        }
    }
    public class Order
    {
        public int Id { get; set; }
        public User User { get; set; }
    }
    public class OrderConfig : IEntityTypeConfiguration
    {
        public void Configure(EntityTypeBuilder builder)
        {
            builder.ToTable("T_Orders");
            builder.HasOne(o => o.User).WithMany()//单向导航属性WithMany参数为空即可
                .IsRequired();
        }
    }

单向导航属性WithMany参数为空即可

多对一

上面一对多双向导航案例中,关系是配置再CommentConfig表中,也可以配置在Article表

builder.HasMany(a => a.Comments).WithOne(c => c.Article).IsRequired();

自组织

class OrgUnit
{
	public long Id { get; set; }
	public string Name { get; set; }
	public OrgUnit Parent { get; set; }
	public List Children { get; set; } = new List();
}
class OrgUnitConfig : IEntityTypeConfiguration
{
    public void Configure(EntityTypeBuilder builder)
    {
        builder.HasOne(u => u.Parent).WithMany(p => p.Children);
    }
}

一对一

如订单、快递单属于一对一关系
必须显式的在其中一个实体类中声明一个外键属性。因为一对一关系,外键建在哪个表都可以,需要我们自己指定一个,否则EF不知道在哪个表创建外键

public class Order
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public Delivery Delivery { get; set; }
}
public class Delivery
{
    public long Id { get; set; }
    public string CompanyName { get; set; }
    public String Number { get; set; }
    public Order Order { get; set; }
    public long OrderId { get; set; }
}
public class OrderConfig : IEntityTypeConfiguration
{
    public void Configure(EntityTypeBuilder builder)
    {
        builder.ToTable("T_Orders");
        builder.HasOne(o => o.Delivery).WithOne(d => d.Order).HasForeignKey(d => d.OrderId);
    }
}

多对多

多对多需要中间表,EF会为我们创建中间表

class Student
{
	public long Id { get; set; }
	public string Name { get; set; }
	public List Teachers { get; set; } = new List();
}
class Teacher
{
	public long Id { get; set; }
	public string Name { get; set; }
	public List Students { get; set; } = new List();
}
builder.HasMany(s => s.Teachers).WithMany(t=>t.Students).UsingEntity(j=>j.ToTable("T_Students_Teachers"));

UsingEntity()方法并不是必须的,我们可以通过它指定中间表名称

sql

插入数据

using (var ctx = new TestDbContext())
{
    Article article = new Article { Title = "文章1", Content = "文章1" };
    article.Comments.Add(new Comment { Message = "评论11" });
    article.Comments.Add(new Comment { Message = "评论12" });
    ctx.Articles.Add(article);
    ctx.SaveChanges();
}

查询关联属性

如果需要查询关联属性,需要使用Include(),最终会生成Join查询sql

using (var ctx = new TestDbContext())
{
    var article = ctx.Articles.Include(a => a.Comments).FirstOrDefault(a => a.Id == 1);
    Console.WriteLine($"articleTitle:{article.Title}");
    foreach (var comment in article.Comments)
    {
        Console.WriteLine($"commentMessage:{comment.Message}");
    }
}

查询评论中含有“微软”的所有的文章:

ctx.Articles.Where(a=>a.Comments.Any(c=>c.Message.Contains("微软")));

另一种方法:

ctx.Comments.Where(c => c.Message.Contains("微软")).Select(c => c.Article).Distinct();