.NET 云原生架構(gòu)師訓(xùn)練營(yíng)(模塊二 基礎(chǔ)鞏固 EF Core 查詢)--學(xué)習(xí)筆記
2.4.5 EF Core -- 查詢
關(guān)聯(lián)數(shù)據(jù)加載
客戶端與服務(wù)端運(yùn)算
跟蹤與不跟蹤
復(fù)雜查詢運(yùn)算
原生 SQL 查詢
全局查詢篩選器
關(guān)聯(lián)數(shù)據(jù)加載
學(xué)員和助教都在項(xiàng)目分組中,調(diào)整模型,刪除 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 添加 學(xué)員信息
public Member Member { get; set; }
接下來(lái)為每一個(gè)表添加一個(gè)控制器
一個(gè) Project 對(duì)應(yīng)多個(gè) 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 主鍵添加自動(dòng)生成
///
/// 主鍵Id
///
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
啟動(dòng)程序,Postman 訪問(wèn)

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);
}
查詢項(xiàng)目信息,發(fā)現(xiàn)分組信息 groups 為空

因?yàn)?EF 默認(rèn)不會(huì)查詢關(guān)聯(lián)數(shù)據(jù),所以需要實(shí)現(xiàn)一下
ProjectController 獲取項(xiàng)目時(shí)使用 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);
}
由于項(xiàng)目中有分組引用,分組中有項(xiàng)目引用,所以需要在序列化的時(shí)候處理循環(huán)引用
Startup
services.AddControllers()
.AddNewtonsoftJson(x=>x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
這樣就可以查到項(xiàng)目信息

EF Core 為我們提供了三種加載數(shù)據(jù)的方式
預(yù)先加載
顯式加載
延遲加載
加載相關(guān)數(shù)據(jù):https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/
預(yù)先加載
預(yù)先加載表示從數(shù)據(jù)庫(kù)中加載關(guān)聯(lián)數(shù)據(jù),作為初始查詢的一部分。
在以下示例中,結(jié)果中返回的blogs將使用關(guān)聯(lián)的posts填充其 Posts 屬性。
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ToList();
}
顯式加載
顯式加載表示稍后從數(shù)據(jù)庫(kù)中顯式加載關(guān)聯(lián)數(shù)據(jù)。
可以通過(guò) DbContext.Entry(...) API 顯式加載導(dǎo)航屬性。
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);
延遲加載
延遲加載表示在訪問(wèn)導(dǎo)航屬性時(shí),從數(shù)據(jù)庫(kù)中以透明方式加載關(guān)聯(lián)數(shù)據(jù)。
使用延遲加載的最簡(jiǎn)單方式是通過(guò)安裝 Microsoft.EntityFrameworkCore.Proxies 包,并通過(guò)調(diào)用 UseLazyLoadingProxies 來(lái)啟用該包。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString);
或在使用 AddDbContext 時(shí):
.AddDbContext
(
b => b.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString));
EF Core 接著會(huì)為可重寫的任何導(dǎo)航屬性(即,必須是 virtual 且在可被繼承的類上)啟用延遲加載。例如,在以下實(shí)體中,Post.Blog 和 Blog.Posts 導(dǎo)航屬性將被延遲加載。
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// 引用到屬性時(shí)才加載
客戶端與服務(wù)端運(yùn)算
客戶端與服務(wù)端運(yùn)算:https://docs.microsoft.com/zh-cn/ef/core/querying/client-eval
由于 SQL Server 提供程序不了解此方法的實(shí)現(xiàn)方式,因此無(wú)法將其轉(zhuǎn)換為 SQL。查詢的所有其余部分是在數(shù)據(jù)庫(kù)中評(píng)估的,但通過(guò)此方法傳遞返回的 URL 卻是在客戶端上完成。
var blogs = context.Blogs
.OrderByDescending(blog => blog.Rating)
.Select(blog => new
{
Id = blog.BlogId,
Url = StandardizeUrl(blog.Url)// 服務(wù)端轉(zhuǎn)換SQL,不了解客戶端方法實(shí)現(xiàn)
})
.ToList();
public static string StandardizeUrl(string url)
{
url = url.ToLower();
if (!url.StartsWith("http://"))
{
url = string.Concat("http://", url);
}
return url;
}
需要區(qū)分?jǐn)?shù)據(jù)運(yùn)算最終在客戶端,還是服務(wù)端運(yùn)行
循環(huán)中獲取分組會(huì)導(dǎo)致多次查詢數(shù)據(jù)庫(kù)
foreach (var project in _lighterDbContext.Projects)
{
project.Groups// 多次查詢數(shù)據(jù)庫(kù)
}
應(yīng)該一次性查詢
var projects = _lighterDbContext.Projects.ToList();
跟蹤與不跟蹤
跟蹤與不跟蹤:https://docs.microsoft.com/zh-cn/ef/core/querying/tracking
默認(rèn)情況下,跟蹤返回實(shí)體類型的查詢。這表示可以更改這些實(shí)體實(shí)例,然后通過(guò) SaveChanges() 持久化這些更改。
非跟蹤查詢
var blogs = context.Blogs
.AsNoTracking()
.ToList();
還可以在上下文實(shí)例級(jí)別更改默認(rèn)跟蹤行為:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var blogs = context.Blogs.ToList();
復(fù)雜查詢運(yùn)算
復(fù)雜查詢運(yùn)算:https://docs.microsoft.com/zh-cn/ef/core/querying/complex-query-operators
聯(lián)接
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);
所有實(shí)體都繼承了基類 Entity,所以這樣會(huì)把過(guò)濾器添加在所有查詢上面
LighterDbContext
modelBuilder.Entity
().HasQueryFilter(x => x.TenantId ==
"");
GitHub源碼鏈接:
https://github.com/MINGSON666/Personal-Learning-Library/tree/main/ArchitectTrainingCamp/LighterApi
課程鏈接
.NET云原生架構(gòu)師訓(xùn)練營(yíng)講什么,怎么講,講多久
