关系配置

推荐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);
}

![](img\Hello\屏幕截图 2025-03-31 094530.png)

单项导航属性

上面的关系配置的例子中,在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; }//配送公司名称/*895632./85、 public string Number { 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}");
}
}