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

02020407 EF Core基础07-一对多实体类关系配置插入数据查询数据、设置额外的外键字段

02020407 EF Core基础07-一对多实体类&关系配置&插入数据&查询数据、设置额外的外键字段

1. EF Core一对多关系配置(视频3-14)

1.1 实体间关系
1、所谓“关系数据库”
2、复习:数据库表之间的关系:一对一、一对多、多对多。
3、EF Core不仅支持单实体操作,更支持多实体的关系操作。
4、三部曲:实体类中关系属性;FluentAPI关系配置;使用关系操作。
1.2 一对多(one to many):实体类
  • 文章实体类Article、评论实体类Comment。一篇文章对应多条评论。
    • 一篇文章有多个评论,一个评论只对应唯一的文章。
public class Article
{public long Id { get; set; }public string Title { get; set; }public string Message { get; set; }public List<Comment> Comments { get; set; } = new List<Comment>(); // 建一个空的list,而不用默认的null。 
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
class Comment // 评论
{public long Id { get; set; }public Article TheArticle { get; set; }public string Message { get; set; }
}
1.3 EF Core中实体之间的关系配置
EF Core中实体之间关系的配置的套路:
HasXXX(…).WithXXX(…); // 有xxx.带着xxx。
XXX可选值One、Many。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
一对多:HasOne(…).WithMany(…);
一对一:HasOne(…).WithOne (…);
多对多:HasMany (…).WithMany(…);
1.4 一对多:关系配置
class ArticleConfig : IEntityTypeConfiguration<Article> // 一段
{public void Configure(EntityTypeBuilder<Article> builder){builder.ToTable("T_Articles");builder.Property(a => a.Content).IsRequired().IsUnicode();builder.Property(a => a.Title).IsRequired().IsUnicode().HasMaxLength(255);}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
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();builder.Property(c=>c.Message).IsRequired().IsUnicode();}
}说明:一对多的关系配置,即可以配置到一端,也可以配置到多端。老师一般配置到一端,这段代码配置到了多端。

2. 一对多示例

2.1 创建实体类和配置类
// OneToMany.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>
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
</Project>
// Article.cs
using System.Collections.Generic;namespace OneToMany
{class Article // 文章{public long Id { get; set; }public string Title { get; set; }public string Message { get; set; }public List<Comment> Comments { get; set; } = new List<Comment>(); // 建一个空的list,而不用默认的null。}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// ArticleConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;namespace OneToMany
{class ArticleConfig : IEntityTypeConfiguration<Article>{public void Configure(EntityTypeBuilder<Article> builder){builder.ToTable("T_Articles");builder.Property(a => a.Title).HasMaxLength(100).IsUnicode().IsRequired();builder.Property(a => a.Message).IsUnicode().IsRequired();}}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// Comment.cs
namespace OneToMany
{class Comment // 评论{public long Id { get; set; }public Article TheArticle { get; set; }public string Message { get; set; }}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// CommentConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;namespace OneToMany
{class CommentConfig : IEntityTypeConfiguration<Comment>{public void Configure(EntityTypeBuilder<Comment> builder){builder.ToTable("T_Comments");builder.Property(c => c.Message).HasMaxLength(100).IsUnicode().IsRequired(); // 在Comment表建一个指向Article的外键builder.HasOne<Article>(a => a.TheArticle).WithMany(c => c.Comments).IsRequired();}}
}—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// MyDbContext.cs
using Microsoft.EntityFrameworkCore;
using System;namespace OneToMany
{class MyDbContext : DbContext{public DbSet<Article> Articles { get; set; }public DbSet<Comment> Comments { 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);}}
}
2.2 完成数据库迁移
1、迁移生成数据库表。
2、编写代码测试数据插入。
3、不需要显式为Comment对象的Article属性赋值(当前赋值也不会出错),也不需要显式地把新创建的Comment类型的对象添加到DbContext中。EF Core会“顺竿爬”。
  • 完成数据库迁移,查看外键信息:SSMS → 表 → T_Comments → 设计 → TheArticleId → 右键 → 关系
图片链接丢失
  • 说明:
    • 有些公司可能因为性能或者迁移的问题,不允许建立外键。关于这一块以后会讲解。
    • 我们并没有在Article实体类中建立TheArticleId这个属性,但是数据库中自动生成了TheArticleId这个外键。
2.3 插入数据
// Program.cs
using System;namespace OneToMany
{class Program{static void Main(string[] args){using (MyDbContext ctx = new MyDbContext()){// 创建文章Article article01 = new Article { Title = "杨中科被评为亚洲最酷程序员" , Message = "据报道:" };// 创建评论Comment comment01 = new Comment { Message = "太牛了!!!"};Comment comment02 = new Comment { Message = "吹吧!!!" };// Article的Comments属性是List类型。article01.Comments.Add(comment01);article01.Comments.Add(comment02);// 更新数据库ctx.Add(article01); // @1 将article01插入数据库,注意此时不用将comment01和comment02插入数据库。// ctx.Add(comment01); // 不写// ctx.Add(comment02); // 不写ctx.SaveChanges();}Console.WriteLine("数据插入成功!!!");}}
}控制台输出:
数据插入成功!!!说明:
1. 在@1处,comment01和comment02可以不用手动加入数据库,EF Core会顺着关系自动将它们两个插入到数据库。
2. 在@1处也可以手动将comment01和comment02插入数据库,但是没有必要。
2.4 查看数据库信息
// T_Articles表
Id	Title	Message
1	杨中科被评为亚洲最酷程序员	据报道:
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// T_Comments表
Id	TheArticleId	Message
1	1	太牛了!!!
2	1	吹吧!!!

3. 一对多关系数据的获取(视频3-15)

3.1 获取关系数据
Article a = ctx.Articles.Include(a=>a.Comments).Single(a=>a.Id==1);
Console.WriteLine(a.Title);
foreach(Comment c in a.Comments)
{Console.WriteLine(c.Id+":"+c.Message);
}
Include定义在Microsoft.EntityFrameworkCore命名空间中。
查看一下生成的SQL语句
3.2 获取数据示例:【正向查 → 通过Article(一)查Comments(多)】
  • 在2.4基础上做如下演示
using System;
using System.Linq;namespace OneToMany
{class Program{static void Main(string[] args){using (MyDbContext ctx = new MyDbContext()){Article a01 = ctx.Articles.Single(a => a.Id == 1); // 确认有且只有Id为1的文章用SingleConsole.WriteLine(a01.Id);Console.WriteLine(a01.Title);foreach (Comment c in a01.Comments) // 遍历所有Comment{Console.WriteLine(c.Id + "," + c.Message);}}Console.WriteLine("数据获取成功!!!");}}
}控制台输出:
杨中科被评为亚洲最酷程序员
dbug: 2025/9/21 23:31:58.540 CoreEventId.ContextDisposed[10407] (Microsoft.EntityFrameworkCore.Infrastructure)'MyDbContext' disposed.
数据获取成功!!!说明:此时没有输出Comment的内容。// 查看SQL语句
SELECT TOP(2) [t].[Id], [t].[Message], [t].[Title]FROM [T_Articles] AS [t]WHERE [t].[Id] = CAST(1 AS bigint)说明:SQL语句并没有LEFT JOIN语句,那么此时只是在T_Articles表里面查询数据了,并没有在T_Comments表里面查询。因此没有输出Comment的内容。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 在需要查询的关联字段上加上Include方法
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;namespace OneToMany
{class Program{static void Main(string[] args){using (MyDbContext ctx = new MyDbContext()){Article a01 = ctx.Articles.Include(a => a.Comments).Single(a => a.Id == 1); // Include方法将关联的Comments的值也给查询出来Console.WriteLine(a01.Id);Console.WriteLine(a01.Title);foreach (Comment c in a01.Comments) // 遍历所有Comment{Console.WriteLine(c.Id + "," + c.Message);}}Console.WriteLine("数据获取成功!!!");}}
}控制台输出:
杨中科被评为亚洲最酷程序员
1,太牛了!!!
2,吹吧!!!
dbug: 2025/9/21 23:39:48.366 CoreEventId.ContextDisposed[10407] (Microsoft.EntityFrameworkCore.Infrastructure)'MyDbContext' disposed.
数据获取成功!!!// 查看SQL语句SELECT [t0].[Id], [t0].[Message], [t0].[Title], [t1].[Id], [t1].[Message], [t1].[TheArticleId]FROM (SELECT TOP(2) [t].[Id], [t].[Message], [t].[Title]FROM [T_Articles] AS [t]WHERE [t].[Id] = CAST(1 AS bigint)) AS [t0]LEFT JOIN [T_Comments] AS [t1] ON [t0].[Id] = [t1].[TheArticleId]ORDER BY [t0].[Id], [t1].[Id]说明:此时有JOIN操作。
3.3 获取数据示例:【反向查 → 通过Comments(多)查Article(一)】
// Program.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;namespace OneToMany
{class Program{static void Main(string[] args){using (MyDbContext ctx = new MyDbContext()){Comment cmt = ctx.Comments.Single(c => c.Id == 2);Console.WriteLine(cmt.Message);Console.WriteLine(cmt.TheArticle.Id + "," + cmt.TheArticle.Title);}Console.WriteLine("数据获取成功!!!");}}
}抛出异常:System.NullReferenceException:“Object reference not set to an instance of an object.”// 查看SQL语句
SELECT TOP(2) [t].[Id], [t].[Message], [t].[TheArticleId]FROM [T_Comments] AS [t]WHERE [t].[Id] = CAST(2 AS bigint)说明:此时没有INNER JOIN查询T_Articles表。因此此时TheArticle的值为null,进而抛出异常。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;namespace OneToMany
{class Program{static void Main(string[] args){using (MyDbContext ctx = new MyDbContext()){Comment cmt = ctx.Comments.Include(c => c.TheArticle).Single(c => c.Id == 2); // Include方法Console.WriteLine(cmt.Message);Console.WriteLine(cmt.TheArticle.Id + "," + cmt.TheArticle.Title);}Console.WriteLine("数据获取成功!!!");}}
}控制台输出:
吹吧!!!
1,杨中科被评为亚洲最酷程序员
dbug: 2025/9/21 23:48:35.193 CoreEventId.ContextDisposed[10407] (Microsoft.EntityFrameworkCore.Infrastructure)'MyDbContext' disposed.
数据获取成功!!!// 查看SQL语句SELECT TOP(2) [t].[Id], [t].[Message], [t].[TheArticleId], [t0].[Id], [t0].[Message], [t0].[Title]FROM [T_Comments] AS [t]INNER JOIN [T_Articles] AS [t0] ON [t].[TheArticleId] = [t0].[Id]WHERE [t].[Id] = CAST(2 AS bigint)

4. 通过EF Core设置额外的外键字段(视频3-16)

4.1 数据库优化原则
1. 数据库中尽量不要select * from t,这样会获取表中所有的值。
2. 推荐用select id, name from t,获取针对性的数据,这样性能更高。
4.2 直接rticle数据表第一条数据
// 形式1:获取当前列所有数据
using System;
using System.Linq;namespace OneToMany
{class Program{static void Main(string[] args){using (MyDbContext ctx = new MyDbContext()){var a01 = ctx.Articles.First();Console.WriteLine(a01.Title + "," + a01.Id);}Console.WriteLine("数据获取成功!!!");}}
}控制台输出:
杨中科被评为亚洲最酷程序员
dbug: 2025/9/22 20:48:58.930 CoreEventId.ContextDisposed[10407] (Microsoft.EntityFrameworkCore.Infrastructure)'MyDbContext' disposed.
数据获取成功!!!// 查看SQL语句
SELECT TOP(1) [t].[Id], [t].[Message], [t].[Title]FROM [T_Articles] AS [t]说明:我只想获取Title和Id,不想获取Message,但是EF Core生成的SQL语句也获取Message了。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 形式2:获取当前列所有数据
using System;
using System.Linq;namespace OneToMany
{class Program{static void Main(string[] args){using (MyDbContext ctx = new MyDbContext()){var a02 = ctx.Articles.Select(a => a).First(); // 通过匿名类型来获取。Console.WriteLine(a02.Title + "," + a02.Id);}Console.WriteLine("数据获取成功!!!");}}
}控制台输出:
杨中科被评为亚洲最酷程序员,1
dbug: 2025/9/22 21:01:01.888 CoreEventId.ContextDisposed[10407] (Microsoft.EntityFrameworkCore.Infrastructure)'MyDbContext' disposed.
数据获取成功!!!// 查看SQL语句SELECT TOP(1) [t].[Id], [t].[Message], [t].[Title]FROM [T_Articles] AS [t]
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
总结:形式1和形式2获取形式等价
4.3 通过匿名类型获取Article表第一条数据
  • 优化SQL语句形式1:通过匿名类型来查询
using System;
using System.Linq;namespace OneToMany
{class Program{static void Main(string[] args){using (MyDbContext ctx = new MyDbContext()){var a02 = ctx.Articles.Select(a => new { a.Id, a.Title }).First(); // 通过匿名类型来获取。Console.WriteLine(a02.Title + "," + a02.Id);}Console.WriteLine("数据获取成功!!!");}}
}控制台输出:
杨中科被评为亚洲最酷程序员,1
dbug: 2025/9/22 20:55:50.718 CoreEventId.ContextDisposed[10407] (Microsoft.EntityFrameworkCore.Infrastructure)'MyDbContext' disposed.
数据获取成功!!!// 查看SQL语句
SELECT TOP(1) [t].[Id], [t].[Title]FROM [T_Articles] AS [t]说明:
1. 通过匿名类型获取,此时没有查询Message信息了。只取出来了Title和Id。
2. 通过匿名类型获取指定的数据,是数据库优化的一种形式。
4.4 通过额外外键获取数据
  • 适用于只获取外键Id,不获取外键形式。为了避免性能损失(不JOIN另外一个表),可以增加一个额外的外键属性。
说明:
1、EF Core会在数据表中建外键列。
2、如果需要获取外键列的值,就需要做关联查询,效率低。在4.2中已经试了。
3、需要一种不需要Join直接获取外键列的值的方式。解决方法:
1、在实体类中显式声明一个外键属性。
2、关系配置中通过HasForeignKey(c=>c.ArticleId)指定这个属性为外键。
3、除非必要,否则不用声明,因为会引入重复。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// step1:单独给Comment.cs实体类增加一个属性TheArticleId。
namespace OneToMany
{class Comment // 评论{public long Id { get; set; }public Article TheArticle { get; set; }public string Message { get; set; }public long TheArticleId { get; set; } // 实体类增加一个外键属性(同数据库中自动生成的TheArticleId外键相同)}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// step2:在CommentConfig.cs配置类中指定TheArticleId属性为外键列。
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;namespace OneToMany
{class CommentConfig : IEntityTypeConfiguration<Comment>{public void Configure(EntityTypeBuilder<Comment> builder){builder.ToTable("T_Comments");builder.Property(c => c.Message).HasMaxLength(100).IsUnicode().IsRequired();builder.HasOne<Article>(a => a.TheArticle).WithMany(c => c.Comments).HasForeignKey(c => c.TheArticleId).IsRequired(); // 声明外键列}}
}
说明:
1. 这里必须声明外键列,否则会在数据库中生成TheArticleId和TheArticleId1两个列。
2. 注意,这里并没有修改数据库,因此不用再程序包管理器中重新使用migration和update-database迁移数据库。当然,执行这两个语句也可以,数据库不会有变化。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
using System;
using System.Linq;namespace OneToMany
{class Program{static void Main(string[] args){using (MyDbContext ctx = new MyDbContext()){var cmt = ctx.Comments.Single(c => c.Id == 1);Console.WriteLine(cmt.Id + "," + cmt.TheArticleId);}Console.WriteLine("数据获取成功!!!");}}
}控制台输出:
1,1
dbug: 2025/9/22 21:20:43.001 CoreEventId.ContextDisposed[10407] (Microsoft.EntityFrameworkCore.Infrastructure)'MyDbContext' disposed.
数据获取成功!!!// 查看SQL语句
SELECT TOP(2) [t].[Id], [t].[Message], [t].[TheArticleId]FROM [T_Comments] AS [t]WHERE [t].[Id] = CAST(1 AS bigint)说明:
1. 此时只查询了Comment这个表,没有JOIN查询Article这个表。只查询单表,这样效率更高。
2. 这种形式知道即可,非必要不要使用。因为会额外增加实体类的属性和增加配置,增加了不确定性。

5. EF Core性能怎么样?

  • 使用EF Core查询比绝大部分程序员写出来的SQL性能都要高。
  • EF Core有少部分SQL语句性能可能不尽如人意,但是也影响不大。
    • 特殊的一些SQL语句可能影响性能瓶颈,咱们在要特殊优化。

结尾

书籍: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=16253

相关文章:

  • 解码数据结构基础
  • 软件工程学习日志2025.9.24
  • 大厂代码编写习惯简谈
  • 知识导航新体验:Perplexica+cpolar 24小时智能服务 - 教程
  • 《计算机算法设计与分析》系列--算法实现题1.1-统计数字问题
  • 银河麒麟系统root密码重置
  • 银河麒麟系统磁盘管理
  • 浅谈傅里叶级数
  • js遍历对象
  • day 10 (函数2 )
  • 入驻了爱发电
  • 奖励函数(双足)
  • 离线部署镜像仓库搭建
  • Temporal和Airflow有什么差别
  • lc1035-不相交的线
  • 自我介绍与未来规划
  • 解构React Server Components:服务端序列化与流式传输的底层逻辑
  • js里面的单引号、双引号及反引号的用法
  • 牛客刷题-Day4
  • Skinned Mesh Renderer与LOD系统蒙皮变形异常全解析
  • K8S (Containerd)初始化安装流程
  • ?模拟赛 赛后总结
  • 日志|动态规划|最长回文子串|最长公共子序列|HTML CSS
  • Java 字段命名避坑: success和isSuccess
  • OTA升级时软件异常复位问题分析
  • Atcoder Educational DP Contest 做题记录
  • 20250924
  • 跨端边云时序数据管理新范式:Apache IoTDB 的 DB+AI 融合之道 - 实践
  • 《Real-Time Rendering》第二章 图形渲染管线
  • 放弃Unity后,我为什么选择了Unigine?