2.4.5 EF Core 查询
一、EF Core查询
1.1示例代码:
关联数据加载、学员和助教都在项目分组中,调整模型,删除 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; }
1.2EF Core为我们提供了三种加载数据的方式
官方文档:https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/
- 预先加载
- 显式加载
- 延迟加载
1.2.1预先加载
表示从数据库中加载关联数据,作为初始查询的一部分。
在以下示例中,结果中返回的blogs将使用关联的posts填充其 Posts 属性
using (var context = new BloggingContext()) { var blogs = context.Blogs .Include(blog => blog.Posts) .ToList(); }
1.2.2显式加载
显式加载表示稍后从数据库中显式加载关联数据。
可以通过 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);
1.2.3延迟加载
延迟加载表示在访问导航属性时,从数据库中以透明方式加载关联数据。
使用延迟加载的最简单方式是通过安装 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();
三、EF Core跟踪与不跟踪
官方文档: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
Join
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,所以这样会把过滤器添加在所有查询上面
modelBuilder.Entity<Entity>().HasQueryFilter(x => x.TenantId == "");