天天看點

.NET 雲原生架構師訓練營(子產品二 基礎鞏固 EF Core 查詢)--學習筆記2.4.5 EF Core -- 查詢

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();
}           
// 顯式加載
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; }           
// 延遲加載
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 查詢:

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/MINGSON666/Personal-Learning-Library/tree/main/ArchitectTrainingCamp/LighterApi