2.4.5 EF Core -- 查询

  • 关联数据加载
  • 客户端与服务端运算
  • 跟踪与不跟踪
  • 复杂查询运算
  • 原生 SQL 查询
  • 全局查询筛选器

关联数据加载

学员和助教都在项目分组中,调整模型,删除 Assistant

ProjectGroup 添加 Member 列表

public List<Member> Members { get; set; }

Member 添加 是否助教判断,分组信息

public bool IsAssistant { get; set; }

public string GroupId { get; set; }

public ProjectGroup Group { get; set; }

Task 添加 学员信息

public Member Member { get; set; }

接下来为每一个表添加一个控制器

一个 Project 对应多个 ProjectGroup

ProjectGroup

namespace LighterApi.Controller
{
[ApiController]
[Route("api/[controller]")]
public class ProjectGroupController : ControllerBase
{
private readonly LighterDbContext _lighterDbContext; public ProjectGroupController(LighterDbContext lighterDbContext)
{
_lighterDbContext = lighterDbContext;
} [HttpPost]
public async Task<IActionResult> Create([FromBody] ProjectGroup group)
{
_lighterDbContext.ProjectGroups.Add(group);
await _lighterDbContext.SaveChangesAsync(); return StatusCode((int) HttpStatusCode.Created, group);
} [HttpGet]
[Route("{id}")]
public async Task<IActionResult> GetAsync(string id, CancellationToken cancellationToken)
{
var project = await _lighterDbContext.Projects.FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
return Ok(project);
}
}
}

迁移

dotnet ef migrations add RefactoryProjectEntities

dotnet ef database update

Entity 主键添加自动生成

/// <summary>
/// 主键Id
/// </summary>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }

启动程序,Postman 访问

ProjectController

[HttpGet]
[Route("{id}")]
public async Task<IActionResult> GetAsync(string id, CancellationToken cancellationToken)
{
var project = await _lighterDbContext.Projects.FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
return Ok(project);
}

查询项目信息,发现分组信息 groups 为空

因为 EF 默认不会查询关联数据,所以需要实现一下

ProjectController 获取项目时使用 Include

[HttpGet]
[Route("{id}")]
public async Task<IActionResult> GetAsync(string id, CancellationToken cancellationToken)
{
var project = await _lighterDbContext.Projects.Include(p => p.Groups)
.FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
return Ok(project);
}

由于项目中有分组引用,分组中有项目引用,所以需要在序列化的时候处理循环引用

Startup

services.AddControllers()
.AddNewtonsoftJson(x=>x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);

这样就可以查到项目信息

EF Core 为我们提供了三种加载数据的方式

  • 预先加载
  • 显式加载
  • 延迟加载

加载相关数据:https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/

预先加载

预先加载表示从数据库中加载关联数据,作为初始查询的一部分。

在以下示例中,结果中返回的blogs将使用关联的posts填充其 Posts 属性。

using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ToList();
}

显式加载

显式加载表示稍后从数据库中显式加载关联数据。

可以通过 DbContext.Entry(...) API 显式加载导航属性。

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();
}

ProjectController

// 显式加载
var project = await _lighterDbContext.Projects.FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
await _lighterDbContext.Entry(project).Collection(p => p.Groups).LoadAsync(cancellationToken);

延迟加载

延迟加载表示在访问导航属性时,从数据库中以透明方式加载关联数据。

使用延迟加载的最简单方式是通过安装 Microsoft.EntityFrameworkCore.Proxies 包,并通过调用 UseLazyLoadingProxies 来启用该包。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString);

或在使用 AddDbContext 时:

.AddDbContext<BloggingContext>(
b => b.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString));

EF Core 接着会为可重写的任何导航属性(即,必须是 virtual 且在可被继承的类上)启用延迟加载。 例如,在以下实体中,Post.Blog 和 Blog.Posts 导航属性将被延迟加载。

public class Blog
{
public int Id { get; set; }
public string Name { get; set; } public virtual ICollection<Post> Posts { get; set; }
} public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; } public virtual Blog Blog { get; set; }
}

Project

public virtual ICollection<ProjectGroup> Groups { get; set; }

ProjectController

// 延迟加载
project.Groups// 引用到属性时才加载

客户端与服务端运算

客户端与服务端运算:https://docs.microsoft.com/zh-cn/ef/core/querying/client-eval

由于 SQL Server 提供程序不了解此方法的实现方式,因此无法将其转换为 SQL。 查询的所有其余部分是在数据库中评估的,但通过此方法传递返回的 URL 却是在客户端上完成。

var blogs = context.Blogs
.OrderByDescending(blog => blog.Rating)
.Select(blog => new
{
Id = blog.BlogId,
Url = StandardizeUrl(blog.Url)// 服务端转换SQL,不了解客户端方法实现
})
.ToList(); public static string StandardizeUrl(string url)
{
url = url.ToLower(); if (!url.StartsWith("http://"))
{
url = string.Concat("http://", url);
} return url;
}

需要区分数据运算最终在客户端,还是服务端运行

循环中获取分组会导致多次查询数据库

foreach (var project in _lighterDbContext.Projects)
{
project.Groups// 多次查询数据库
}

应该一次性查询

var projects = _lighterDbContext.Projects.ToList();

跟踪与不跟踪

跟踪与不跟踪:https://docs.microsoft.com/zh-cn/ef/core/querying/tracking

默认情况下,跟踪返回实体类型的查询。 这表示可以更改这些实体实例,然后通过 SaveChanges() 持久化这些更改。

非跟踪查询

var blogs = context.Blogs
.AsNoTracking()
.ToList();

还可以在上下文实例级别更改默认跟踪行为:

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

var blogs = context.Blogs.ToList();

复杂查询运算

复杂查询运算:https://docs.microsoft.com/zh-cn/ef/core/querying/complex-query-operators

联接

var query = from photo in context.Set<PersonPhoto>()
join person in context.Set<Person>()
on photo.PersonPhotoId equals person.PhotoId
select new { person, photo };

GroupJoin

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

SelectMany

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

GroupBy

var query = from p in context.Set<Post>()
group p by p.AuthorId into g
select new
{
g.Key,
Count = g.Count()
};

Left Join

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

原生 SQL 查询

原生 SQL 查询:https://docs.microsoft.com/zh-cn/ef/core/querying/raw-sql

var blogs = context.Blogs
.FromSqlRaw("SELECT * FROM dbo.Blogs")
.ToList();

全局查询筛选器

全局查询筛选器:https://docs.microsoft.com/zh-cn/ef/core/querying/filters

modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "_tenantId") == _tenantId);
modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);

所有实体都继承了基类 Entity,所以这样会把过滤器添加在所有查询上面

LighterDbContext

modelBuilder.Entity<Entity>().HasQueryFilter(x => x.TenantId == "");

GitHub源码链接:

https://github.com/MingsonZheng/ArchitectTrainingCamp/tree/main/LighterApi

课程链接

https://appsqsyiqlk5791.h5.xiaoeknow.com/v1/course/video/v_5f39bdb8e4b01187873136cf?type=2

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

.NET 云原生架构师训练营(模块二 基础巩固 EF Core 查询)--学习笔记的更多相关文章

  1. .NET 云原生架构师训练营(权限系统 RGCA 开发任务)--学习笔记

    目录 目标 模块拆分 OPM 开发任务 目标 基于上一讲的模块划分做一个任务拆解,根据任务拆解实现功能 模块拆分 模块划分已经完成了边界的划分,边界内外职责清晰 OPM 根据模块拆分画出 OPM(Ob ...

  2. .NET 云原生架构师训练营(权限系统 代码实现 ActionAccess)--学习笔记

    目录 开发任务 代码实现 开发任务 DotNetNB.Security.Core:定义 core,models,Istore:实现 default memory store DotNetNB.Secu ...

  3. .NET 云原生架构师训练营(权限系统 代码实现 WebApplication)--学习笔记

    目录 开发任务 代码实现 开发任务 DotNetNB.Security.Core:定义 core,models,Istore:实现 default memory store DotNetNB.WebA ...

  4. .NET 云原生架构师训练营(权限系统 系统演示 ActionAccess)--学习笔记

    目录 模块拆分 环境配置 默认用户 ActionAccess 模块拆分 环境配置 mysql migration mysql docker pull mysql docker run -p 3306: ...

  5. .NET 云原生架构师训练营(权限系统 系统演示 EntityAccess)--学习笔记

    目录 模块拆分 EntityAccess 模块拆分 EntityAccess 实体权限 属性权限 实体权限 创建 student https://localhost:7018/Student/dotn ...

  6. .NET 云原生架构师训练营(权限系统 代码实现 EntityAccess)--学习笔记

    目录 开发任务 代码实现 开发任务 DotNetNB.Security.Core:定义 core,models,Istore:实现 default memory store DotNetNB.Secu ...

  7. .NET 云原生架构师训练营(权限系统 代码实现 Identity)--学习笔记

    目录 开发任务 代码实现 开发任务 DotNetNB.Security.Core:定义 core,models,Istore:实现 default memory store DotNetNB.Secu ...

  8. .NET 云原生架构师训练营(模块一 架构师与云原生)--学习笔记

    目录 什么是软件架构 软件架构的基本思路 单体向分布式演进.云原生.技术中台 1.1 什么是软件架构 1.1.1 什么是架构? Software architecture = {Elements, F ...

  9. .NET 云原生架构师训练营(建立系统观)--学习笔记

    目录 目标 ASP .NET Core 什么是系统 什么是系统思维 系统分解 什么是复杂系统 作业 目标 通过整体定义去认识系统 通过分解去简化对系统的认识 ASP .NET Core ASP .NE ...

  10. .NET 云原生架构师训练营(权限系统 RGCA 架构设计)--学习笔记

    目录 项目核心内容 实战目标 RGCA 四步架构法 项目核心内容 无代码埋点实现对所有 API Action 访问控制管理 对 EF Core 实体新增.删除.字段级读写控制管理 与 Identity ...

随机推荐

  1. 使用element-plus的el-scrollbar时滚动条没有显示出来但是页面可以滚动的解决办法

    如果使用 Element UI 的 el-scrollbar 组件时,滚动条没有显示出来但页面可以滚动,可以尝试调用其 update 方法来更新滚动条. 在适当的时机(例如在数据加载完成后或组件更新后 ...

  2. Go 疑难杂症汇总

    1. revision v0.0.0: unknown revision v0.0.0 go get -u github.com/uudashr/gopkgs/cmd/gopkgs 报错: [root ...

  3. STM32CubeMX教程20 SPI - W25Q128驱动

    1.准备材料 开发板(正点原子stm32f407探索者开发板V2.4) STM32CubeMX软件(Version 6.10.0) 野火DAP仿真器 keil µVision5 IDE(MDK-Arm ...

  4. centos7 systemctl配置开机自启动服务

    centos7使用systemctl替代原来/etc/init.d,按官方的说法是提高系统服务的运行效率.服务配置更加简单易用,对于一些自定义的服务来配置开机自启动,是真的香! 概念理解 它是服务管理 ...

  5. 分享一个简单的使用js格式化json的代码

    今天给大家分享一段json格式化代码. 假设json字符串是: {"name":"刘德华","age":25.2,"birthda ...

  6. 【rt-thread】Kconfig文件添加子Kconfig文件时是以顶级Kconfig所在目录为当前路径的

    示例如下 顶级Kconfig文件所在目录 子级Kconfig文件所在目录 子级Kconfig文件添加次子级Kconfig文件,以顶级目录为当前路径依次写出次子级Kconfig文件所在目录

  7. 【C++】类大小

    [来源]C++类大小详尽讲解 [来源]空类(empty class)

  8. [转帖]CentOS-7-x86_64-Everything-2009 rpm包列表(CentOS7.9)

    CentOS-7-x86_64-Everything-2009 rpm包列表(CentOS7.9) 共10073个文件 复制389-ds-base-1.3.10.2-6.el7.x86_64.rpm ...

  9. [转帖]调试springboot数据库系统应用时常用debug日志配置, 解决问题缩小范围时常用

    https://www.yihaomen.com/article/1853.html 摘要: 用 spring boot 开发应用时,在遇到麻烦问题时,经常会打开debug日志,下面记录一个通用的思路 ...

  10. Mysql Server System Variables [官网资料]

    5.1.7 Server System Variables The MySQL server maintains many system variables that configure its op ...