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

共 8293字,需浏览 17分钟

 ·

2021-01-06 12:29



2.4.5 EF Core -- 查询



  • 关联数据加载


  • 客户端与服务端运算


  • 跟踪与不跟踪


  • 复杂查询运算


  • 原生 SQL 查询


  • 全局查询筛选器



关联数据加载


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


ProjectGroup 添加 Member 列表


public List

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

Create([FromBody] ProjectGroup
group)

{

_lighterDbContext.ProjectGroups.Add(
group);


await _lighterDbContext.SaveChangesAsync();




return StatusCode((
int) HttpStatusCode.Created,
group);

}



[
HttpGet]

[
Route("{id}")]


public
async Task

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 主键添加自动生成


/// 
/// 主键Id
///

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }

启动程序,Postman 访问



ProjectController


[HttpGet]
[Route("{id}")]
public async Task

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

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

(


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

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

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

()


join person
in context.Set

()


on photo.PersonPhotoId
equals person.PhotoId


select
new { person, photo };




GroupJoin


var query = from b in context.Set<Blog>()
join p in context.Set

()

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

()

select new {
b, p };



GroupBy


var query = from p in context.Set

()


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

()

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

().HasQueryFilter(b => EF.Property<
string>(b,
"_tenantId") == _tenantId);

modelBuilder.Entity

().HasQueryFilter(p => !p.IsDeleted);




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


LighterDbContext


modelBuilder.Entity

().HasQueryFilter(x => x.TenantId ==
"");



GitHub源码链接:


https://github.com/MINGSON666/Personal-Learning-Library/tree/main/ArchitectTrainingCamp/LighterApi


课程链接


.NET云原生架构师训练营讲什么,怎么讲,讲多久





欢迎各位读者加入微信群一起学习交流,


在公众号后台回复“加群”即可~~









浏览 35
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报