0 前言

本文会列举出 EF Core 关联查询的方法:

在第一、二、三节中,介绍的是 EF Core 的基本能力,在实体中配置好关系,即可使用,且其使用方式,与编程思维吻合,是本文推荐的方式。

第四节中,将介绍 Linq 语句的两种关联查询方式:分别是 lambda 方式和 query 方式。

1 概述

数据库中,表与表之间可能是有一定关联关系的,在查询数据过程中,我们经常会用到关联查询(常见的关联查询有如:inner join、left join 等)。

而在程序中,使用 EF Core 写关联查询语句是比较难写的(或者说大部分 ORM 工具都是如此)。

其实 EF Core 提供了关系的配置,通过简单一些设置,可以让我们以代码的思维,去处理这些有关联关系的数据。

下面举个栗子:

官方例子的 Blog 和 Post 是一对多的关系,如果要查出某个 Blog 下的 Post,正常在数据库中,我们会写连接语句,大概如下:

SELECT b.BlogId, b.Url, p.PostId, p.Title, p.Context
FROM Blog b
LEFT JOIN Post p ON b.BlogId = p.BlogId
WHERE b.BlogId = 1

通过左连接(left join)进行关联查询。

而在 EF Core 中,如果我们建立起关系,以及配置好相应的导航属性,可以直接将实体关联起来,通过实体操作与实体关联的其他实体(如官方的例子,通过 Blog 操作与 Blog 关联的 Post):

var blog = db.Blogs.Include(b => b.Posts) // 关联 Post
.Where(t => t.BlogId == 1) // 查出 BlogId = 1 的记录
.FirstOrDefault(); // 查出第一条记录 var url = blog.Url; // 获取 blog 的 url 属性
var postCount = blog.Posts.Count; // 获取与 Blog 关联的 Post 的数量
var title = blog.Posts[0].Title;

这种关联关系,在程序中操作,其实是很方便的。

2 基本实现

下面将会以一个简单的例子实现,具体可以查看官方关系的内容。

2.1 配置导航属性

如下面,将会建立 Blog 和 Post 之间一对多的关系:

public class Blog // 主体实体
{
public int BlogId { get; set; } // 主体键
public string Url { get; set; } public List<Post> Posts { get; set; } // 集合导航属性
} public class Post // 依赖实体
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; } public int BlogId { get; set; } // 外键
public Blog Blog { get; set; } // 引用导航属性
}

Post.BlogBlog.Posts 的反向导航属性(反之亦然)

2.2 添加 DbSet

在自定义的 DbContext 中添加相应对应 DbSet:

public class EDbContext : DbContext
{
public virtual DbSet<Blog> Blogs { get; set; }
public virtual DbSet<Post> Posts { get; set; } public EDbContext() { } public EDbContext(DbContextOptions<EDbContext> options) : base(options) { } protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer("server=localhost;database=EfCoreRelationship;uid=sa;pwd=Qwe123456;");
base.OnConfiguring(options);
} protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}

2.3 查询

如此即可进行查询:

var blog = db.Blogs.Include(b => b.Posts) // 关联 Post
.Where(t => t.BlogId == 2) // 查出 BlogId = 2 的记录
.FirstOrDefault(); // 查出第一条记录 var url = blog.Url; // 获取 blog 的 url 属性
var postCount = blog.Posts.Count; // 获取与 Blog 关联的 Post 的数量
var title = blog.Posts[0].Title;

3 补充

这一节,将是对基本实现的一些内容的补充。

3.1 手动配置关系

基本实现中,没有在自定义的 DbContext 中明确配置关系,可以如下配置:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts);
}

3.2 阴影属性(shadow property)

如果依赖实体(如例子中 Post 实体)上,没有明确定义外键(如例子中 BlogId),具体可以查看阴影和索引器属性

public class Blog // 主体实体
{
public int BlogId { get; set; } // 主体键
public string Url { get; set; } public List<Post> Posts { get; set; } // 集合导航属性
} public class Post // 依赖实体
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; } // 自动引入 BlogId 阴影属性
public Blog Blog { get; set; } // 引用导航属性
}

3.3 级联删除

参考级联删除

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.OnDelete(DeleteBehavior.Cascade);
}

DeleteBehavior 枚举值及其定义,可以参考DeleteBehavior 枚举

3.4 补充:加载相关数据

参考加载相关数据

3.4.1 预先加载

查询的时候,使用 Include 方法。

var blogs = db.Blogs.Include(b => b.Posts) // 关联 Post
.ToList();

包含多个层级,使用 ThenInclude

var blogs = db.Blogs.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList();

3.4.2 显式加载

using (var context = new BloggingContext())
{
var blog = context.Blogs
.Single(b => b.BlogId == 1); context.Entry(blog)
.Collection(b => b.Posts)
.Load(); context.Entry(blog)
.Reference(b => b.Owner)
.Load();
}

3.4.3 延迟加载

相关数据的延迟加载

4 Linq 语句实现关联查询

4.1 Lambda 方式

lambda 方式实现 EF Core 左连接查询(left join),使用 SelectMany 方法:

版本1:

var blogs = _db.Set<Blog>()
.SelectMany(b => _db.Set<Post>().Where(p => b.BlogId == p.BlogId).DefaultIfEmpty(),
(b, ps) => new { b.Url, ps.Title })
.ToList();

版本2:

var blogs = _db.Set<Blog>()
.GroupJoin(_db.Set<Post>(),
b => b.BlogId,
p => p.BlogId, (b, p) => new { b, p })
.SelectMany(n => n.p.DefaultIfEmpty(),
(n, p) => new { n.b.Url, p!.Title })
.ToList();

两个版本的 sql 语句(都是一样的):

SELECT [b].[Url], [p].[Title]
FROM [Blog] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]

4.2 Query 方式

两表关联 query 方式基本写法:

var query = from b in context.Set<Blog>()
join p in context.Set<Post>()
on b.BlogId equals p.BlogId
select new { b, p };

其他写法(实际上是基于 SelectMany 方法):

var query = from b in context.Set<Blog>()
from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId)
select new { b, p }; var query2 = from b in context.Set<Blog>()
from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId).DefaultIfEmpty()
select new { b, p };

对应的 sql 语句:

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId] SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

参考来源

EF Core 官方文档:关系

EF Core 官方文档:复杂查询运算符

EF Core 的关联查询的更多相关文章

  1. 深入理解 EF Core:使用查询过滤器实现数据软删除

    原文:https://bit.ly/2Cy3J5f 作者:Jon P Smith 翻译:王亮 声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的.其中可能会去除一些本人实在不知道如何组织 ...

  2. EF Core 使用编译查询提高性能

    今天,我将向您展示这些EF Core中一个很酷的功能,通过使用显式编译的查询,提高查询性能. 不过在介绍具体内容之前,需要说明一点,EF Core已经对表达式的编译使用了缓存:当您的代码需要重用以前执 ...

  3. EF core的原生SQL查询以及用EF core进行分页查询遇到的问题

    在用.net core进行数据库访问,需要处理一些比较复杂的查询,就不得不用原生的SQL查询了,然而EF Core 和EF6 的原生sql查询存在很大的差异. 在EF6中我们用SqlQuery和Exe ...

  4. ef实现左关联查询

    在EF中,当在dbset使用join关联多表查询时,连接查询的表如果没有建立相应的外键关系时,EF生成的SQL语句是inner join(内联),对于inner join,有所了解的同学都知道,很多时 ...

  5. .ef core 多对对关系的关联方法

    最近在用.net core 重构博客,在使用ef core连表查询时,遇到了一些问题.记录一下. 关系:一个博客可以有多个标签,一个标签可以属于多个博客,博客和标签之间存在多对多的关系 下面是实体代码 ...

  6. EF Core 三 、 骚操作 (导航属性,内存查询...)

    EF Core 高阶操作 本文之前,大家已经阅读了前面的系列文档,对其有了大概的了解 我们来看下EF Core中的一些常见高阶操作,来丰富我们业务实现,从而拥有更多的实现选择 1.EF 内存查找 wh ...

  7. EF 6.x、EF Core实现dynamic动态查询和EF Core实现多个上下文实例池你了解多少?

    前言 很长一段时间没有写博客了,今天补上一篇吧,偶尔发现不太愿意写博客了,太耗费时间,不过还是在坚持当中,毕竟或许写出来的东西能帮到一些童鞋吧,接下来我们直奔主题.无论是在在EF 6.x还是EF Co ...

  8. 讨论过后而引发对EF 6.x和EF Core查询缓存的思考

    前言 最近将RabbitMQ正式封装引入到.NET Core 2.0项目当中,之前从未接触过是个高大上的东东跟着老大学习中,其中收获不少,本打算再看看RabbitMQ有时间写写,回来后和何镇汐大哥探讨 ...

  9. [翻译 EF Core in Action 2.3] 理解EF Core数据库查询

    Entity Framework Core in Action Entityframework Core in action是 Jon P smith 所著的关于Entityframework Cor ...

随机推荐

  1. 集合流之"交集(相同)和差集(区别的)"的使用

    一.需求 今天做的是将两个字符串转为数组后再转集合,然后利用集合的流stream来进行差集过滤 二.差集代码 差集:将两个集合相同的数据去掉,留下不同的数据 1 @Test 2 public void ...

  2. Pandas怎样新增数据列

    Pandas怎样新增数据列? 在进行数据分析时,经常需要按照一定条件创建新的数据列,然后进行进一步分析. 直接赋值 df.apply方法 df.assign方法 按条件选择分组分别赋值 0.读取csv ...

  3. 2018 百度web前端面试

    面试前 正式入职一年半左右,实习半年,勉强两年经验吧,然后很惊喜收到了百度的面试邀约,约得两点钟面试,然后本人一点钟就到了,通电话之后,面试官很热情,说正在吃饭吃完饭就去找我,让我去坐着等一会,然后一 ...

  4. PokemonGo:LBS游戏开发

    写在前面 去吧!皮卡丘!小时候拥有一台任天堂是多少熊孩子的梦想,每个夜晚被窝里透出的微弱光线,把小小的童年带入另一个世界,家门口的鸟和狗,森林里的虫和瀑布,山洞里的超音蝠,带着小小的梦,走过一个个城市 ...

  5. post提交的数据有哪几种编码格式?能否通过URL参数获取用户账户密码

    这里是修真院前端小课堂,每篇分享文从 [背景介绍][知识剖析][常见问题][解决方案][编码实战][扩展思考][更多讨论][参考文献] 八个方面深度解析前端知识/技能,本篇分享的是: [post提交的 ...

  6. 大数据学习之路之sqoop导入

    按照网上的代码导入 hadoop(十九)-Sqoop数据清洗 - 简书 (jianshu.com) ./sqoop import --connect "jdbc:mysql://192.16 ...

  7. python---输出函数运行时间的装饰器

    """ 装饰器: 不改变原函数的调用方式和函数的前提下, 增加额外的功能, 其本质就是一个闭包 ---输出函数的运行时间 """ impor ...

  8. 记录Jenkins升级到最新版遇到的问题

    ​ 首先吐槽一下Jenkins: 1.安装插件的时候无法根据Jenkins的版本号安装对应的插件! 2.安装插件安装版本不一致的时候无法降低插件版本! 3.为啥要我们升级到最新版! 一.升级原因 我的 ...

  9. 服务器的cpu 核心、线程

    此版本有大范围改动,因为cpu作为一个大脑,所以更细致的进行了,相关的分析和阐述. 1.版本1. 2022.1.242.版本2: 2022.3.2 采集数据: ht2机器为物理机,cpu是4颗cpu, ...

  10. Jx.Cms开发笔记(四)-改造Card组件

    在Blazor 组件库 BootstrapBlazor 中Card组件介绍中我们说过,如果我们使用了Card组件的IsCollapsible属性设置了可伸缩的话,就只能使用Text属性来设置标题文本, ...