当前位置: 首页 > news >正文

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查看表
Typora-Logo
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}; // @1ctx.Add(dl01); // 此时存od01是不可以的,因为od01没有关联两个对象。而dl01中Order = od01关联了两个对象。ctx.SaveChanges(); }Console.WriteLine("数据插入成功!!!");}}
}
控制台输出:
数据插入成功!!!说明:在@1处,Order = od01使得两个对象产生关系,保存时会自动给外键赋值。我们不需要显式的外键赋值。// 查看数据库
T_Orders表
1	书	NULLT_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	书	NULLT_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 1FROM [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※

※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※

http://www.hskmm.com/?act=detail&tid=25352

相关文章:

  • 02020503 EF Core高级03-分页查询、IQuerable底层的实现形式、DataReader、DataTable、EF Core中的异步方法
  • 02020502 EF Core高级02-IQuerable会延迟执行、分部和动态构建IQuerable、IQuerable的复用
  • 在 PyCharm 中,环境:bert_env , 执行 import wandb 报错。但是,在CMD窗口,环境:bert_env , 执行 import wandb 正常。
  • Linux_T(Sticky Bit)粘滞位详解 - 详解
  • P2831 [NOIP 2016 提高组] 愤怒的小鸟 题解
  • 库存中心(三层库存模型)
  • Valley靶机渗透实战:从凭证复用到Python库劫持
  • 10.05模拟赛反思
  • MariaDB收购SkySQL增强AI与无服务器能力
  • 单片机寄存器的四种主要类型! - 实践
  • TDengine 高级特性——读缓存
  • 非合作博弈之软性均衡:东方智慧与西方理论的融合框架
  • 如何快速搭建spring-boot工程 - murphy
  • Ai元人文:东谈西论——非合作博弈之软性均衡
  • 反向传播与梯度下降:神经网络如何真正学会分类
  • Spring Cloud Alibaba微服务开发
  • OI 各种东西的板子
  • 价值弥漫:AI元人文的场域革命与共生之路
  • 做题记录 #1
  • 阿爸阿爸
  • 深度学习优化器算法巧思速览
  • 完整教程:LangChain完全指南:从入门到精通,打造AI应用开发新范式
  • NDK开发与实践(入门篇微课视频版)
  • 调了很久的代码总结
  • CF700E
  • 价值弥漫:“AI元人文”的场域革命与共生之路
  • k8s之pod概念
  • CF 1055 Div.1+Div.2
  • 2026 NOI 做题记录(五)
  • ARC 207 (Div.1)