P4-6 关系配置

关系配置


说明

  • 1、所谓“关系数据库”
  • 2、复习:数据库表之间的关系:一对一、一对多、多对多。
  • 3、EF Core不仅支持单实体操作,更支持多实体的关系操作。
  • 4、三部曲:
    • 实体类中关系属性
    • FluentAPI关系配置(这个是最复杂的)
    • 使用关系操作

一对多

实体类

  • 文章实体类Article
  • 评论实体类Comment
  • 一篇文章对应多条评论。
    • 文章
    • 评论

关系配置

  • EF Core中实体之间关系的配置的套路:

    • HasXXX(…).WithXXX(…);
    • 有XXX、反之带有XXX。
      • XXX可选值One(一)、Many(多)
  • 一对多:HasOne(…).WithMany(…);

  • 一对一:HasOne(…).WithOne (…);

  • 多对多:HasMany (…).WithMany(…);
    多端配置
    一对多


关联数据的获取

一对多

  • 获取关系数据(用EF Core常用下面两个)
    • using Microsoft.EntityFrameworkCore;
    • using System.Linq;
      1
      2
      3
      4
      5
      6
      7
      8
      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语句
  • 如果没有Include
    • 去掉Include,再看一下运行效果。
      • 去掉后,不会去用join连接另外一个表,无法多表联查
  • 查看生成的SQL分析为什么。
    • A连接B查询 和B连接A查询 运行的SQL查询语句不一样
    • LEFT JOIN / INNER JOIN
    • 两种连接
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      SELECT [t0].[Id], [t0].[Content], [t0].[Title], [t1].[Id], [t1].[ArticleId], [t1].[Message]
      FROM (
      SELECT TOP(2) [t].[Id], [t].[Content], [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].[ArticleId]
      ORDER BY [t0].[Id], [t1].[Id]


      SELECT TOP(2) [t].[Id], [t].[ArticleId], [t].[Message], [t0].[Id], [t0].[Content], [t0].[Title]
      FROM [T_Comments] AS [t]
      INNER JOIN [T_Articles] AS [t0] ON [t].[ArticleId] = [t0].[Id]
      WHERE [t].[Id] = CAST(1 AS bigint)

实体类对象的关联追踪

  • 只要我们的代码把实体类的关系设定了,EF Core就会顺竿爬
1
2
3
4
5
6
7
8
9
10
11
12
//关联添加数据
Article a1 = new Article();
a1.Title = "关于.NET 5正式发布";
a1.Content = ".NET是升级版";
Comment c1 = new Comment { Message = "已经再用了", Article = a1 };
Comment c2 = new Comment { Message = "不错不错", Article = a1 };
using (MyDbContext ctx = new MyDbContext())
{
ctx.Comments.Add(c1);
ctx.Comments.Add(c2);
await ctx.SaveChangesAsync();
}

EF Core的“顺杆爬”

顺杆爬

  • 不需要显式为Comment对象的Article属性赋值(当前赋值也不会出错),也不需要显式地把新创建的Comment类型的对象添加到DbContext中。
  • EF Core会“顺竿爬”。
  • 杨中科老师的习惯是写全,不习惯让它顺杆爬,因为顺杆爬写着写着有可能自己都乱套了

关系的外键属性的设置

为什么需要额外的外键属性

  • 了解即可,具体适合的案例可以看实体书中本章的内容

EF Core的SQL语句质量

  • 数据库优化的原则
    • 尽量不要select * from XXX
    • 可以获取需要的列即可,会提升性能(但用EF Core的话,一般情况下不需要进行这种优化)
  • EF Core自动生成的SQL语句质量
    • 大部分查询比绝大部分程序员写出来的SQL性能都要高
    • 只有少部分SQL语句性能可能不尽如人意,但是也影响不大。
      • 特殊的一些SQL语句可能影响性能瓶颈,咱们在需要特殊优化

设置外键属性

  • 1、在实体类中显式声明一个外键属性。
    • 步骤1
  • 2、关系配置中通过HasForeignKey(c=>c.ArticleId)指定这个属性为外键。
    • 步骤2
    • 步骤3
  • 3、除非必要,否则不用声明,因为会引入重复。
1
2
3
4
5
6
7
8
9
10
//关系的外键属性的设置
var a1 = ctx.Articles.Select(a => new { a.Id, a.Title }).First();
Console.WriteLine(a1.Id+","+a1.Title);

var cmt = ctx.Comments.Select(c=> new { Id=c.Id,Aid=c.Article.Id}).Single(c => c.Id == 1);//只获取我们需要的列数据
Console.WriteLine(cmt.Id + "," + cmt.Aid);

//加了外键的
var cmt = ctx.Comments.Single(c => c.Id == 1);//只获取我们需要的列数据
Console.WriteLine(cmt.Id + "," + cmt.ArticleId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//没加外键
SELECT TOP(1) [t].[Id], [t].[Title]
FROM [T_Articles] AS [t]


SELECT TOP(2) [t].[Id], [t0].[Id] AS [Aid]
FROM [T_Comments] AS [t]
INNER JOIN [T_Articles] AS [t0] ON [t].[ArticleId] = [t0].[Id]
WHERE [t].[Id] = CAST(1 AS bigint)

//加了外键的
SELECT TOP(2) [t].[Id], [t].[ArticleId], [t].[Message]
FROM [T_Comments] AS [t]
WHERE [t].[Id] = CAST(1 AS bigint)

单项导航属性

  • 对于主从结构的“一对多”表关系,一般是声明双向导航属性。
    • 比如之前学的文章和评论的案例
  • 而对于其他的“一对多”表关系:如果表属于被很多表引用的基础表,则用单项导航属性,否则可以自由决定是否用双向导航属性。
    • 比如我有请假单,报销单,离职单等 但是都需要对应用户表。这种情况就适用于单项导航

单项导航属性的使用步骤

  • 1.配置一端和多端
    • 多对一的单项
    • 不设置反向的属性,然后配置的时候WithMany()不设置参数即可。
  • 单项属性如果反向获取数据
1
2
3
4
User u1 = new User { Name = "鑫Cool" };
Leave l1 = new Leave { Remarks = "回家处理事情", Requester = u1 };
ctx.Leaves.Add(l1);//根据多端去顺杆爬,插入User的信息
ctx.SaveChanges();

关系配置在哪个实体类中

  • 北京固安县和涿州市的故事
    • 案例png
    • 固安县叫中间这个路的名字为“固涿路”;而涿州市叫“涿固路”
    • 站在不同的角度,就有不同的说法,但是本质上它们指的是同一个东西
    • 所以两张表的关系配置可以在任何一端
  • 文章Article和评论Comment
    • 文章叫“一对多” ; 而评论叫“多对一”
    • 反向配置
1
2
3
builder.HasMany<Comment>(a => a.Comments).WithOne(c => c.Article).IsRequired();//一对多

builder.HasOne<Article>(c => c.Article).WithMany(a => a.Comments).IsRequired();//多对一
  • 一对多的推荐策略

    • 考虑到有单项导航属性的可能,我们一般用HasOne().WithMany()
    • 建议都配置到多的那一端
  • 自引用的组织结构树

    • 一种特殊的一对多案例
      • 特殊一对多

一对一

  • 一对一关系

    • 比如:订单–>快递单
  • 一对一关系配置

    • 1、 builder.HasOne<Delivery>(o => o.Delivery).WithOne(d => d.Order).HasForeignKey<Delivery>(d=>d.OrderId);
    • 2、测试插入和获取数据。
    • 注意:必须显式的在其中一个实体类中声明一个外键属性。
1
2
3
4
5
6
7
8
9
10
11
12
Order order = new Order();
order.Address = "测试区涿州市";
order.Name = "3C充电器";
Delivery delivery = new Delivery();
delivery.CompanyName = "快到天际快递";
delivery.Number = "SN666888";
delivery.Order = order;
ctx.Deliveries.Add(delivery);
await ctx.SaveChangesAsync();//插入

Order o1 = await ctx.Orders.Include(o => o.Delivery).FirstAsync(o => o.Name.Contains("充电器"));//查询
Console.WriteLine($"名称:{o1.Name},单号:{o1.Delivery.Number}");

多对多

  • 描述
    • 多对多的案例:老师–>学生
    • 从EF Core 5.0开始,才正式支持多对多关系的支持
      • 但还是需要中间表来保存两张表之间的对应关系
  • 多对多实体和多对多关系配置
    • 多对多关系配置
  • 测试代码
    • 数据插入
    • 查询
    • 插入数据-查询

基于关系的复杂查询

关系数据查询

  • 1、查询评论中含有“微软”的所有的文章
  • 2、同样效果的代码可能有多种写法,有时候要关注底层的SQL,看哪种方式最好。
    • 确保系统在重要环节不会有明显的性能瓶颈
      1
      2
      3
      4
      5
      6
      //第一种 exists
      ctx.Articles.Where(a=>a.Comments.Any(c=>c.Message.Contains("微软")));

      //第二种 inner join
      ctx.Comments.Where(c => c.Message.Contains("微软")).Select(c => c.Article).Distinct();

例子

  • 1、查询“所有由蜗牛快递负责的订单信息”
    ctx.Orders.Where(o=>o.Delivery.CompanyName== “蜗牛快递”);

P4-6 关系配置
http://example.com/2024/09/22/Net Core2022教程/第4章:Entity Framework Core基础/P4-6 关系配置/
Author
John Doe
Posted on
September 22, 2024
Licensed under