zoukankan      html  css  js  c++  java
  • .NET 云原生架构师训练营(模块二 基础巩固 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();
    }
    

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

    知识共享许可协议

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

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

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

  • 相关阅读:
    Tsinsen-A1489 抽奖 【数学期望】
    jmeter正则表达式
    robotframwork的WEB功能测试(一)—切换window窗口
    Appium基础篇(一)——启动emulator
    20161114记录一件工作的事
    robotframwork数据库测试(一)——pymysql与pyodbc
    robotframwork接口测试(四)—其他库的安装
    robotframwork接口测试(三)—HttpLibrary.HTTP安装
    robotframework接口测试(二)—post request
    robotframework接口测试(一)—Get request json
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/14213770.html
Copyright © 2011-2022 走看看