关系配置 推荐IDE:JetBrains Rider
一对多 一对多是最常见的实体类的关系。比如文章和评论的关系就是一对多的关系,也就是一篇文章对应多条评论。
文章
1 2 3 4 5 6 7 class Article { public long Id { get ; set ; } public string Title { get ; set ; } public string Massage { get ; set ; } public List<Comment> Comments { get ; set ; }= new List<Comment>(); }
评论
1 2 3 4 5 6 class Comment { public long Id { get ; set ; } public string Massage { get ; set ; } public Article TheArticle { get ; set ; } }
EF Core中实体类之间关系的配置采用如下的模式:HasXXX(…).WithYYY(…); Has在英语中是“有”的意思,With在英语中是“带有”的意思,因此HasXXX(…).WithYYY(…)就代表“A实体类对象有XXX个B实体类对象,B实体类对象带有YYY个A实体类对象”。其中HasXXX(…)用来设置当前这个实体类和关联的另一个实体类的关系,WithYYY(…)用来反向配置实体类的关系。XXX、YYY有One和Many这两个可选值。
我们在A实体类中配置builder.HasOne<B>(…).WithMany(…)就表示A和B是“一对多”的关系,也就是一个A实体类的对象对应一个B实体类的对象,而一个B实体类的对象有多个A实体类的对象与之对应;如果在A实体类中配置builder.HasOne<B>(…).WithOne(…)就表示A和B是“一对一”的关系,也就是一个A实体类的对象对应一个B实体类对象,而一个B实体类的对象也有一个A实体类的对象与之对应;如果A实体类中配置builder.HasMany<B>(…).WithMany(…)就表示A和B是“多对多”的关系,也就是一个A实体类的对象对应多个B实体类的对象,而一个B实体类的对象也有多个A实体类的对象与之对应。
使用Fluent API就行关系的配置。
1 2 3 4 5 6 7 8 9 class ArticleConfig :IEntityTypeConfiguration <Article >{ public void Configure (EntityTypeBuilder<Article> builder ) { builder.ToTable("T_Article" ); builder.Property(a=>a.Title).HasMaxLength(100 ).IsUnicode().IsRequired(); builder.Property(a => a.Massage).IsUnicode().IsRequired(); } }
1 2 3 4 5 6 7 8 9 class CommentConfig : IEntityTypeConfiguration <Comment >{ public void Configure (EntityTypeBuilder<Comment> builder ) { builder.ToTable("T_Comment" ); builder.Property(a=>a.Massage).IsUnicode().IsRequired(); builder.HasOne<Article>(c => c.TheArticle).WithMany(a=>a.Comments).IsRequired(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class MyDbContext : DbContext { public DbSet<Article> Articles { get ; set ; } public DbSet<Comment> Comments { get ; set ; } protected override void OnConfiguring (DbContextOptionsBuilder optionsBuilder ) { base .OnConfiguring(optionsBuilder); string connstr="Server=localhost;Database=T_dome;Trusted_Connection=True;MultipleActiveResultSets=true;Encrypt=false;" ; optionsBuilder.UseSqlServer(connstr); optionsBuilder.LogTo(Console.WriteLine); } protected override void OnModelCreating (ModelBuilder modelBuilder ) { base .OnModelCreating(modelBuilder); modelBuilder.ApplyConfigurationsFromAssembly(this .GetType().Assembly); } }
1 builder.HasOne<Article>(c => c.TheArticle).WithMany(a=>a.Comments).IsRequired();
这行代码的意思就是“一条评论对应一篇文章,一篇文章有多条评论”。
HasOne<Article>(c=>c.Article)中的Lambda表达式c=>c.Article表示Comment类的Article属性是指向Article实体类型。
WithMany(a=>a.Comments)表示一个Article对应多个Comment,并且在Article中可以通过Comments属性访问到相关的Comment对象。
IsRequired表示Comment中的Article属性是不可为空的。
其中T_Comment表的Articleid列是一个指向T_Articles表Id列的外键。
插入数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using (MyDbContext ctx=new MyDbContext()){ Article article = new Article(); article.Title = "EF Core" ; article.Massage = "EF Core is good" ; Comment comment1 = new Comment { Massage="太好了" }; Comment comment2 = new Comment { Massage = "太棒了" }; article.Comments.Add(comment1); article.Comments.Add(comment2); ctx.Articles.Add(article); await ctx.SaveChangesAsync(); }
关联数据的获取 EF Core的关系配置不仅能帮我们简化数据的插入,也可以简化数据的获取。把Id==1的文章及评论输出。
1 2 3 4 5 6 Article a=ctx.Articles.Include(a=>a.Comments).Single(a => a.Id == 1 ); Console.WriteLine(a.Title); foreach (var item in a.Comments){ Console.WriteLine(item.Id+";" +item.Massage); }

单项导航属性 上面的关系配置的例子中,在Article类中声明了Comment属性指向Comment类,这样我们不仅可以通过Comment类的Article属性获取评论对应的文章信息,还可以通过Article类的Comments属性获取文章的所有评论信息。这样的关系叫作“双向导航”。
双向导航让我们可以通过任意一方的对象获取对方的信息,但是有时候我们不方便声明双向导航。 比如基础的“用户”实体类会被非常多的其他实体类引用,比如“请假单”中会有“申请者”“批准者”等“用户”实体类型的属性。因此系统中会有几十个甚至上百个实体类都有“用户”实体类的属性,但是“用户”实体类不需要为每个实体类都声明一个导航属性。这种情况下,我们就需要一种只在“多端”声明导航属性,而不需要在“一端”声明导航属性的单项导航机制。
这种单项导航属性的配置只要在WithMany()方法中不指定属性即可。
创建实体类
1 2 3 4 5 public class User { public long Id { get ; set ; } public string Name { get ; set ; } }
1 2 3 4 5 6 7 8 9 namespace ConsoleApp4 ;public class Leave { public long Id { get ; set ; } public User Requester { get ; set ; } public User? Approver { get ; set ; } public string Remark { get ; set ; } }
声明实体类的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class MyDbContext :DbContext { public DbSet<User> Users { get ; set ; } public DbSet<Leave> Leaves { get ; set ; } protected override void OnConfiguring (DbContextOptionsBuilder optionsBuilder ) { string connStr = "Server=localhost;Database=T_dome;Trusted_Connection=True;MultipleActiveResultSets=true;Encrypt=false;" ; optionsBuilder.UseSqlServer(connStr); } protected override void OnModelCreating (ModelBuilder modelBuilder ) { base .OnModelCreating(modelBuilder); modelBuilder.ApplyConfigurationsFromAssembly(this .GetType().Assembly); } }
编写Leave类的配置
1 2 3 4 5 6 7 8 public class LeaveConfig : IEntityTypeConfiguration <Leave >{ public void Configure (EntityTypeBuilder<Leave> builder ) { builder.HasOne<User>(l => l.Requester).WithMany().IsRequired(); builder.HasOne<User>(l => l.Approver).WithMany(); } }
插入数据到数据库
1 2 3 4 5 6 7 8 9 using (MyDbContext db = new MyDbContext()){ User user = new User{Name= "John" }; Leave u1=new Leave{Remark = "Holiday" ,Requester = user}; db.Leaves.Add(u1); db.SaveChangesAsync(); }
一对一 假设实现一个电商网站,那么“订单”和“快递信息”可以定义两个实体类,这两个实体类之间就是一对一的关系:一个订单对应一个快递信息,一个快递信息对应一个订单。
订单实体类Order
1 2 3 4 5 6 7 class Order { public long Id { get ; set ; } public string Name { get ; set ; } public string Address { get ; set ; } public Delivery? Delivery { get ; set ; } }
快递信息实体类Delivery
1 2 3 4 5 6 7 8 class Delivery { public long Id { get ; set ; } public string CompanName { get ; set ; } public Order? Order { get ; set ; } public long OrderId { get ; set ; } }
对于一对一关系,由于双方是“平等”的关系,外键列可以建在任意一方,因此我们必须显式地在其中一个实体类中声明一个外键属性。就像上面的实体类定义中,Delivery类中声明了一个外键属性OrderId,当然我们也可以改成在Order类中声明一个外键属性DeliveryId,效果一样的。
1 2 3 4 5 6 7 8 9 10 11 class OrderConfig : IEntityTypeConfiguration <Order > { public void Configure (EntityTypeBuilder<Order> builder ) { builder.ToTable("T_Order" ); builder.Property(o=>o.Address).IsRequired(); builder.Property(o => o.Name).IsRequired(); builder.HasOne<Delivery>(o=>o.Delivery).WithOne(d=>d.Order).HasForeignKey<Delivery>(d => d.OrderId); *-9 +6 3 } }
1 2 3 4 5 6 7 8 9 10 class OrderConfig : IEntityTypeConfiguration <Order >{ public void Configure (EntityTypeBuilder<Order> builder ) { builder.ToTable("T_Order" ); builder.Property(o=>o.Address).IsRequired(); builder.Property(o => o.Name).IsRequired(); builder.HasOne<Delivery>(o=>o.Delivery).WithOne(d=>d.Order).HasForeignKey<Delivery>(d => d.OrderId); } }
和一对多关系类似,在一对一关系中,把关系放到哪一方的实体类的配置中都可以。
插入数据及查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using (MyDbContext db = new MyDbContext()){ db.Database.EnsureDeleted(); db.Database.EnsureCreated(); Order order = new Order { Name = "订单1" , Address = "地址1" , Delivery = new Delivery { CompanName = "公司1" , Number = "123456" } }; db.Add(order); db.SaveChanges(); Console.WriteLine("添加成功" ); Order order1 = db.Set<Order>().Include(o => o.Delivery).FirstOrDefault(); Console.WriteLine($"订单名称:{order1.Name} ,订单地址:{order1.Address} ,配送公司:{order1.Delivery?.CompanName} ,配送单号:{order1.Delivery?.Number} " ); }
多对多 多对多指的是A实体类的一个对象可以被多个B实体类的对象引用,B实体类的一个对象也可以被多个A实体类的对象引用。 一个老师对应多个学生,一个学生对应多个老师,因此老师和学生之间的关系就是多对多。
声明学生类Student和老师类Teacher两个实体类。
1 2 3 4 5 6 class Student { public long Id { get ; set ; } public string Name { get ; set ; } public List<Teacher> Teachers { get ; set ; } }
1 2 3 4 5 6 class Teacher { public long Id { get ; set ; } public string Name { get ; set ; } public List<Student> Students { get ; set ; } }
可以看到,学生类Student中有一个List类型的Teachers代表这个学生的所有老师,同样的 ,老师类Teacher中也有一个List类型的Student代表这个老师的所有学生。
实体类配置
1 2 3 4 5 6 7 8 class TeacherConfig : IEntityTypeConfiguration <Teacher >{ public void Configure (EntityTypeBuilder<Teacher> builder ) { builder.ToTable("Teacher" ); builder.Property(s=>s.Name).IsUnicode().HasMaxLength(50 ); } }
1 2 3 4 5 6 7 8 9 class SudentConfig : IEntityTypeConfiguration <Student >{ public void Configure (EntityTypeBuilder<Student> builder ) { builder.ToTable("T_Student" ); builder.Property(s=>s.Name).IsUnicode().HasMaxLength(50 ); builder.HasMany<Teacher>(s => s.Teachers).WithMany(t => t.Students).UsingEntity(j=>j.ToTable("T_StudentTeacher" )); } }
在多对多关系中,我们必须引入一张额外的数据库表存两张表之间的对应关系。在EFCore中,使用UsingEntity(j=>j.ToTable(“T_StudentTeacher”))的方式配置中间表。
插入,查询数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using (TestDbConText db = new TestDbConText()){ db.Database.EnsureDeleted(); db.Database.EnsureCreated(); Student student = new Student { Name = "学生1" , Teachers = new List<Teacher> { new Teacher{Name="老师1" }, new Teacher{Name="老师2" } } }; db.Add(student); db.SaveChanges(); Console.WriteLine("添加成功" ); Student student1 = db.Set<Student>().Include(s => s.Teachers).FirstOrDefault(); Console.WriteLine($"学生名称:{student1.Name} " ); foreach (var item in student1.Teachers) { Console.WriteLine($"老师名称:{item.Name} " ); } }