【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<Comment> Comments { get; set; } = new List<Comment>(); //此文章的若干条评论
}
class ArticleConfig : IEntityTypeConfiguration<Article>
{
public void Configure(EntityTypeBuilder<Article> 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<Comment>
{
public void Configure(EntityTypeBuilder<Comment> builder)
{
builder.ToTable("T_Comments");
builder.HasOne<Article>(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<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("T_Users");
}
}
public class Order
{
public int Id { get; set; }
public User User { get; set; }
}
public class OrderConfig : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.ToTable("T_Orders");
builder.HasOne<User>(o => o.User).WithMany()//单向导航属性WithMany参数为空即可
.IsRequired();
}
}
单向导航属性WithMany
参数为空即可
多对一
上面一对多双向导航案例中,关系是配置再CommentConfig表中,也可以配置在Article表
builder.HasMany<Comment>(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<OrgUnit> Children { get; set; } = new List<OrgUnit>();
}
class OrgUnitConfig : IEntityTypeConfiguration<OrgUnit>
{
public void Configure(EntityTypeBuilder<OrgUnit> builder)
{
builder.HasOne<OrgUnit>(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<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.ToTable("T_Orders");
builder.HasOne<Delivery>(o => o.Delivery).WithOne(d => d.Order).HasForeignKey<Delivery>(d => d.OrderId);
}
}
多对多
多对多需要中间表,EF会为我们创建中间表
class Student
{
public long Id { get; set; }
public string Name { get; set; }
public List<Teacher> Teachers { get; set; } = new List<Teacher>();
}
class Teacher
{
public long Id { get; set; }
public string Name { get; set; }
public List<Student> Students { get; set; } = new List<Student>();
}
builder.HasMany<Teacher>(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();