02020409 EF Core基础09-一对一、多对多、EF Core基于关系的复杂查询
02020409 EF Core基础09-一对一、多对多、EF Core基于关系的复杂查询
1. 一对一(视频3-20)
1.1 一对一关系
采购申请单 ↔ 采购订单
订单 ↔ 快递单
- 一对一:对方都是对方的唯一。
- 一个订单单对应一个快递单,一个快递单也对应一个订单。
1.2 新建一对一的实体类
class Order // 订单
{
public long Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public Delivery Delivery { get; set;} // Delivery类型的属性,对应哪个快递单。
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
class Delivery // 快递单
{
public long Id { get; set; }
public string CompanyName { get; set; }
public String Number { get; set; }
public Order Order { get; set; } // Order类型的属性,对应哪个订单。
public long OrderId { get; set; }
}
说明:
1. 必须显式的在其中一个实体类中声明一个外键属性。显式声明的外键属性既可以在订单中声明,也可以在快递单中声明,注意只需要建一个就行。
2. 在前面讲到的一对多是否声明外键属性不强制要求显式声明,因为一对多情况下:多的一端是一定有一个外键属性来对应一的一端,会自动生成外键而不需要显式声明。
1.3 一对一关系配置
1、builder.HasOne<Delivery>(o => o.Delivery).WithOne(d => d.Order).HasForeignKey<Delivery>(d=>d.OrderId);
2. 一对一项目示例
2.1 创建项目
- 创建OneToOne控制台项目
// OneToOne.csproj直接添加依赖包
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="microsoft.entityframeworkcore.sqlserver" Version="5.0.4" />
<PackageReference Include="microsoft.entityframeworkcore.tools" Version="5.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// Order.cs
namespace OneToOne
{
class Order
{
public long Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public Delivery Delivery { get; set; } // Delivery类型的属性,对应哪个快递单。
}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// OrderConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace OneToOne
{
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>(o => o.OrderId);
}
}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// Delivery.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OneToOne
{
class Delivery
{
public long Id { get; set; }
public string CompanyName { get; set; }
public String Number { get; set; }
public Order Order { get; set; } // Order类型的属性,对应哪个订单。
public long OrderId { get; set; }
}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// DeliveryConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OneToOne
{
class DeliveryConfig : IEntityTypeConfiguration<Delivery>
{
public void Configure(EntityTypeBuilder<Delivery> builder)
{
builder.ToTable("T_Deliveries");
}
}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// MyDbContext.cs
using Microsoft.EntityFrameworkCore;
using System;
namespace OneToOne
{
class MyDbContext : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<Delivery> Deliveries { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connStr = "Server=.;Database=CoreDataDB;Trusted_Connection=True;MultipleActiveResultSets=true";
optionsBuilder.UseSqlServer(connStr);
// optionsBuilder.LogTo(Console.WriteLine);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}
}
3.2 迁移数据库
PM> add-migration init
Build started...
Build succeeded.
To undo this action, use Remove-Migration.
PM> update-database
Build started...
Build succeeded.
Applying migration '20250923121957_init'.
Done.
PM>
- 通过SSMS查看表
T_Deliveries表和外键关系 |
T_Orders表 |
3.3 插入数据
// 插入数据形式1:通过关联的形式存储数据,顺杆爬。
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
namespace OneToOne
{
class Program
{
static void Main(string[] args)
{
using(MyDbContext ctx = new MyDbContext())
{
Order od01 = new Order { Name = "书" };
Delivery dl01 = new Delivery { CompanyName = "科科快递", Number = "keke001", Order = od01}; // @1
ctx.Add(dl01); // 此时存od01是不可以的,因为od01没有关联两个对象。而dl01中Order = od01关联了两个对象。
ctx.SaveChanges();
}
Console.WriteLine("数据插入成功!!!");
}
}
}
控制台输出:
数据插入成功!!!
说明:在@1处,Order = od01使得两个对象产生关系,保存时会自动给外键赋值。我们不需要显式的外键赋值。
// 查看数据库
T_Orders表
1 书 NULL
T_Deliveries表
Id CompanyName Number OrderId
1 科科快递 keke001 1
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 插入数据形式2:不考虑顺杆爬的问题,直接将所有的数据都存起来。
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
namespace OneToOne
{
class Program
{
static void Main(string[] args)
{
using(MyDbContext ctx = new MyDbContext())
{
Order od01 = new Order { Name = "书" };
Delivery dl01 = new Delivery { CompanyName = "科科快递", Number = "keke001", Order = od01};
// 直接存储,不考虑顺杆爬的问题。
ctx.Orders.Add(od01); // 直接存,不考虑顺杆爬
ctx.Deliveries.Add(dl01); // 直接存,不考虑顺杆爬
ctx.SaveChanges();
}
Console.WriteLine("数据插入成功!!!");
}
}
}
// 查看数据库
T_Orders表
1 书 NULL
T_Deliveries表
Id CompanyName Number OrderId
1 科科快递 keke001 1
3.4 查询数据
using System;
using System.Linq;
namespace OneToOne
{
class Program
{
static void Main(string[] args)
{
using (MyDbContext ctx = new MyDbContext())
{
IQueryable<Order> orders = ctx.Orders.Where(o => o.Delivery.CompanyName == "科科快递"); // 查询科科快递对应的所有订单
foreach (var item in orders)
{
Console.WriteLine(item.Name);
}
}
Console.WriteLine("数据查询成功!!!");
}
}
}
控制台输出:
书
数据查询成功!!!
3. 多对多(视频3-21)
3.1 多对多关系
1、多对多:老师—学生。
2、EF Core 5.0开始,才正式支持多对多
3、需要中间表,举例数据。中间表由EF Core自动生成,我们只需要配置一下中间表的名字即可。
说明:一个老师可以有多个学生,一个学生可以有多个老师。
3.2 新建多对多的实体类
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>();
}
3.3 多对多关系配置
builder.HasMany<Teacher>(s => s.Teachers).WithMany(t=>t.Students).UsingEntity(j=>j.ToTable("T_Students_Teachers"));
说明:UsingEntity()配置中间表,参数为中间表的名称。
4. 多对多项目示例
4.1 创建项目
- 创建ManyToMany控制台项目
// ManyToMany.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="microsoft.entityframeworkcore.sqlserver" Version="5.0.4" />
<PackageReference Include="microsoft.entityframeworkcore.tools" Version="5.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// Student.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ManyToMany
{
class Student
{
public long Id { get; set; }
public string Name { get; set; }
public List<Teacher> Teachers { get; set; } = new List<Teacher>();
}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// StudentConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ManyToMany
{
class StudentConfig : IEntityTypeConfiguration<Student>
{
public void Configure(EntityTypeBuilder<Student> builder)
{
builder.ToTable("T_Students");
builder.HasMany<Teacher>(s => s.Teachers).WithMany(t => t.Students).UsingEntity(j => j.ToTable("T_Students_Teachers"));
}
}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// Teacher.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ManyToMany
{
class Teacher
{
public long Id { get; set; }
public string Name { get; set; }
public List<Student> Students { get; set; } = new List<Student>();
}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// TeacherConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ManyToMany
{
class TeacherConfig : IEntityTypeConfiguration<Teacher>
{
public void Configure(EntityTypeBuilder<Teacher> builder)
{
builder.ToTable("T_Teachers");
}
}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// MyDbContext.cs
using Microsoft.EntityFrameworkCore;
using System;
namespace ManyToMany
{
class MyDbContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Teacher> Teachers { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connStr = "Server=.;Database=CoreDataDB;Trusted_Connection=True;MultipleActiveResultSets=true";
optionsBuilder.UseSqlServer(connStr);
// optionsBuilder.LogTo(Console.WriteLine);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}
}
4.2 迁移数据库
PM> add-migration init
Build started...
Build succeeded.
To undo this action, use Remove-Migration.
PM> update-database
Build started...
Build succeeded.
Applying migration '20250923134033_init'.
Done.
- 查看数据表
4.3 插入数据
// Program.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
namespace ManyToMany
{
class Program
{
static void Main(string[] args)
{
using(MyDbContext ctx = new MyDbContext())
{
Student st01 = new Student { Name = "张三" };
Student st02 = new Student { Name = "李四" };
Student st03 = new Student { Name = "王五" };
Teacher th01 = new Teacher { Name = "Tom" };
Teacher th02 = new Teacher { Name = "Jerry" };
Teacher th03 = new Teacher { Name = "Zack" };
st01.Teachers.Add(th01);
st01.Teachers.Add(th02);
st02.Teachers.Add(th02);
st02.Teachers.Add(th03);
st03.Teachers.Add(th01);
st03.Teachers.Add(th02);
st03.Teachers.Add(th03);
// 所有的学生和老师都插入,避免漏项。虽然麻烦,但是事少。不推荐顺杆爬
ctx.Teachers.Add(th01);
ctx.Teachers.Add(th02);
ctx.Teachers.Add(th03);
ctx.Students.Add(st01);
ctx.Students.Add(st02);
ctx.Students.Add(st03);
ctx.SaveChanges();
}
Console.WriteLine("数据插入成功!!!");
}
}
}
控制台输出:
数据插入成功!!!
// T_Students表
Id Name
1 张三
2 李四
3 王五
// T_Teachers表
Id Name
1 Tom
2 Jerry
3 Zack
// T_Students_Teachers表
StudentsId TeachersId
1 1
3 1
1 2
2 2
3 2
2 3
3 3
4.4 查询数据
// Program.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
namespace ManyToMany
{
class Program
{
static void Main(string[] args)
{
using(MyDbContext ctx = new MyDbContext())
{
var teachers = ctx.Teachers.Include(t => t.Students);
foreach (var th in teachers)
{
Console.WriteLine(th.Name);
foreach (var st in th.Students)
{
Console.WriteLine("\t" + st.Name);
}
}
}
Console.WriteLine("数据查询成功!!!");
}
}
}
控制台输出:
Tom
张三
王五
Jerry
张三
李四
王五
Zack
李四
王五
数据查询成功!!!
5. EF Core基于关系的复杂查询(视频3-22)
5.1 一对多的复杂查询
- 基于02020407章2.1小结项目继续演示
// 通过SSMS在T_Articles表中添加如下数据
Id Title Message
1 杨中科入选中科院 大新闻
2 微软发布.NET 10.0 中新闻
3 微软发射微星 小新闻
4 中国发射小行星探测器 劲爆新闻
// 通过SSMS在T_Comments表中添加如下数据
Id TheArticleId Message
1 1 太牛了
2 2 微软不过如此
3 3 微软真牛
4 4 OMG
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 查询评论中含有“微软”的所有的文章
形式1:ctx.Articles.Where(a=>a.Comments.Any(c=>c.Message.Contains("微软")));
形式2:ctx.Comments.Where(c => c.Message.Contains("微软")).Select(c => c.Article).Distinct();
查看两种形式生成的SQL语句。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 查询形式1:Program.cs
using System;
using System.Linq;
namespace OneToMany
{
class Program
{
static void Main(string[] args)
{
using (MyDbContext ctx = new MyDbContext())
{
// 从Article入手
var items = ctx.Articles.Where(a => a.Comments.Any(c => c.Message.Contains("微软"))); // Article中Comments属性中Message属性包含“微软”的文章。
foreach (var item in items)
{
Console.WriteLine(item.Title);
}
}
Console.ReadLine();
}
}
}
控制台输出:
微软发布.NET 10.0
微软发射微星
// 查看SQL语句
SELECT [t].[Id], [t].[Message], [t].[Title]
FROM [T_Articles] AS [t]
WHERE EXISTS (
SELECT 1
FROM [T_Comments] AS [t0]
WHERE ([t].[Id] = [t0].[TheArticleId]) AND ([t0].[Message] LIKE N'%微软%'))
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 查询形式2:Program.cs
using System;
using System.Linq;
namespace OneToMany
{
class Program
{
static void Main(string[] args)
{
using (MyDbContext ctx = new MyDbContext())
{
IQueryable<Article> items = ctx.Comments.Where(c => c.Message.Contains("微软")).Select(c => c.TheArticle); // 评论的Messages属性包含微软,然后Select(投影)到Article上。
foreach (var item in items)
{
Console.WriteLine(item.Title);
}
}
Console.ReadLine();
}
}
}
控制台输出:
微软发布.NET 10.0
微软发射微星
// 查看SQL语句
SELECT [t0].[Id], [t0].[Message], [t0].[Title]
FROM [T_Comments] AS [t]
INNER JOIN [T_Articles] AS [t0] ON [t].[TheArticleId] = [t0].[Id]
WHERE [t].[Message] LIKE N'%微软%'
5.2 一对多的复杂查询:排重
- 基于本节5.1继续演示
// 通过SSMS在T_Articles表中添加如下数据
Id Title Message
1 杨中科入选中科院 大新闻
2 微软发布.NET 10.0 中新闻
3 微软发射微星 小新闻
4 中国发射小行星探测器 劲爆新闻
// 通过SSMS在T_Comments表中添加如下数据
Id TheArticleId Message
1 1 科科太牛了
2 2 微软不过如此
3 3 微软真牛
4 3 微软真水
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// Program.cs
using System;
using System.Linq;
namespace OneToMany
{
class Program
{
static void Main(string[] args)
{
using (MyDbContext ctx = new MyDbContext())
{
IQueryable<Article> items = ctx.Comments.Where(c => c.Message.Contains("微软")).Select(c => c.TheArticle); // 评论的Messages属性包含微软,然后Select(投影)到Article上。
foreach (var item in items)
{
Console.WriteLine(item.Title);
}
}
Console.ReadLine();
}
}
}
控制台输出:
微软发布.NET 10.0
微软发射微星
微软发射微星
// 查看SQL语句
SELECT [t0].[Id], [t0].[Message], [t0].[Title]
FROM [T_Comments] AS [t]
INNER JOIN [T_Articles] AS [t0] ON [t].[TheArticleId] = [t0].[Id]
WHERE [t].[Message] LIKE N'%微软%'
说明:此时微软发射微星重复了2次。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// Program.cs
using System;
using System.Linq;
namespace OneToMany
{
class Program
{
static void Main(string[] args)
{
using (MyDbContext ctx = new MyDbContext())
{
IQueryable<Article> items = ctx.Comments.Where(c => c.Message.Contains("微软")).Select(c => c.TheArticle).Distinct(); // Distinct方法排重。
foreach (var item in items)
{
Console.WriteLine(item.Title);
}
}
Console.ReadLine();
}
}
}
控制台输出:
微软发布.NET 10.0
微软发射微星
// 查看SQL语句
SELECT DISTINCT [t0].[Id], [t0].[Message], [t0].[Title]
FROM [T_Comments] AS [t]
INNER JOIN [T_Articles] AS [t0] ON [t].[TheArticleId] = [t0].[Id]
WHERE [t].[Message] LIKE N'%微软%'
说明:排重之后,相同的文章只输出一次。
5.3 一对多两种复杂查询方式对比
- 在5.1和5.2小节中,两种查询在应对不同的需求时效率是不同的,按需选择。
- 如果用5.1中有性能瓶颈,那么可以尝试用5.2中的形式,反之亦然。
结尾
书籍:ASP.NET Core技术内幕与项目实战
视频:https://www.bilibili.com/video/BV1pK41137He
著:杨中科
ISBN:978-7-115-58657-5
版次:第1版
发行:人民邮电出版社
※敬请购买正版书籍,侵删请联系85863947@qq.com※
※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※

浙公网安备 33010602011771号