查询数据

Entity Framework Core 使用语言集成查询 (LINQ) 来查询数据库中的数据。 通过 LINQ 可使用 C#(或你选择的其他 .NET 语言)编写强类型查询。 它使用你派生得到的上下文和实体类来引用数据库对象。 EF Core 将 LINQ 查询的表示形式传递给数据库提供程序。 反过来,数据库提供程序将其转换为数据库特定的查询语言(例如,用于关系数据库的 SQL)。

加载所有数据

using (var context = new BloggingContext())
{
    var blogs = context.Blogs.ToList();
}

加载单个实体

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Single(b => b.BlogId == 1);
    var first = context.Blogs.First(b => b.BlogId == 1);
    var firstordefault = context.Blogs.FirstOrDefault(b => b.BlogId == 1);
    var find = context.Blogs.Find(1);
}

筛选

using (var context = new BloggingContext())
{
    var blogs = context.Blogs.Where(b => b.Url.Contains("dotnet")).ToList();
}

排序

using (var context = new BloggingContext())
{
    var blogs = context.Blogs.Where(b => b.Url.Contains("dotnet"))
                .OrderBy(o => o.Id)
                .ToList();
}

正序 OrderBy ,倒序 OrderByDescending,当需要多个排序时,后接 ThenBy 或者 ThenByDescending.

分页

using (var context = new BloggingContext())
{
    var blogs = context.Blogs.Where(b => b.Url.Contains("dotnet"))
                .OrderBy(o => o.Id)
                .Skip((PageIndex-1) * PageSize).Take(PageSize)
                .ToList();
}

Like 模糊查询

通配符 说明 示例
% 包含零 个或多个字符的任意字符串 WHERE title LIKE '%computer%' 将查找 在书名中任意位置包含单词 "computer" 的所有书名。
_(下划线) 任何单个字符 WHERE au_fname LIKE '_ean' 将查找以 ean 结尾的所有 4 个字母的名字(Dean、Sean 等)
\(反斜杠) 匹配百分比符号或下划线 WHERE au_fname LIKE '\_ean' 将查询 _ean
using (var context = new BloggingContext())
{
    var blogs = context.Blogs.Where(u => EF.Functions.Like(u.Name, "%us%")).ToList();
}

StartsWith Like

using (var context = new BloggingContext())
{
    var blogs = context.Blogs.Where(u => u.Name.StartsWith("US%")).ToList();
}

EndsWith Like

using (var context = new BloggingContext())
{
    var blogs = context.Blogs.Where(u => u.Name.EndsWith("%US")).ToList();
}

客户端与服务器评估

一般情况下,Entity Framework Core 会尝试尽可能全面地评估服务器上的查询。 EF Core 将查询的一部分转换为可在客户端评估的参数。 系统将查询的其余部分(及生成的参数)提供给数据库提供程序,以确定要在服务器上评估的等效数据库查询。 EF Core 支持在顶级投影中进行部分客户端评估(基本上为最后一次调用 Select())。 如果查询中的顶级投影无法转换为服务器,EF Core 将从服务器中提取任何所需的数据,并在客户端上评估查询的其余部分。 如果 EF Core 在顶级投影之外的任何位置检测到不能转换为服务器的表达式,则会引发运行时异常。
在某些情况下,可能需要以显式方式强制进行客户端评估,如下所示:
  • 由于数据量小,因此在进行客户端评估时才不会大幅减弱性能。

  • 所用的 LINQ 运算符不会进行任何服务器端转换。
    在这种情况下,通过调用 AsEnumerable 或 ToList 等方法(若为异步,则调用 AsAsyncEnumerable 或 ToListAsync),以显式方式选择进行客户端评估。 使用 AsEnumerable 将对结果进行流式传输,但使用 ToList 将通过创建列表来进行缓冲,因此也会占用额外的内存。
    更多内容请查询EFCore 文档

跟踪与非跟踪查询

跟踪行为决定了 Entity Framework Core 是否将有关实体实例的信息保留在其更改跟踪器中。 如果已跟踪某个实体,则该实体中检测到的任何更改都会在 SaveChanges() 期间永久保存到数据库。 EF Core 还将修复跟踪查询结果中的实体与更改跟踪器中的实体之间的导航属性。
跟踪查询
var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();

非跟踪查询

var blogs = context.Blogs
    .AsNoTracking()
    .ToList();
更多内容请查询EFCore 文档

复杂查询运算符

语言集成查询 (LINQ) 包含许多用于组合多个数据源或执行复杂处理的复杂运算符。 并非所有 LINQ 运算符都会在服务器端进行适当转换。

INNER 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 };

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 };

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(new Post())
            select new { b, p };

GroupJoin

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on photo.PersonPhotoId equals person.PhotoId into os
            select new { person, photo };

SelectMany

var query = from p in context.Set<Post>()
            group p by p.AuthorId into g
            select new
            {
                g.Key,
                Count = g.Count()
            };

GroupBy

var query = from p in context.Set<Post>()
            group p by p.AuthorId into g
            select new
            {
                g.Key,
                Count = g.Count()
            };
更多内容请查询EFCore 文档

加载相关数据

Entity Framework Core 允许你在模型中使用导航属性来加载相关实体。 有三种常见的 ORM 模式可用于加载关联数据。

  • 预先加载表示从数据库中加载关联数据,作为初始查询的一部分。
  • 显式加载表示稍后从数据库中显式加载关联数据。
  • 延迟加载表示在访问导航属性时,从数据库中以透明方式加载关联数据。

预先加载

可以使用 Include 方法来指定要包含在查询结果中的关联数据。 在以下示例中,结果中返回的blogs将使用关联的posts填充其 Posts 属性。

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ToList();
}

可以在单个查询中包含多个关系的关联数据。

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .Include(blog => blog.Owner)
        .ToList();
}

包含多个层级

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .ToList();
}

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
                .ThenInclude(author => author.Photo)
        .ToList();
}

可以将来自多个级别和多个根的关联数据合并到同一查询中

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
                .ThenInclude(author => author.Photo)
        .Include(blog => blog.Owner)
            .ThenInclude(owner => owner.Photo)
        .ToList();
}

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Tags)
        .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();
}

查询相关实体

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    var postCount = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Count();

    var goodPosts = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Where(p => p.Rating > 3)
        .ToList();
}

延迟加载

使用延迟加载的最简单方式是通过安装 Microsoft.EntityFrameworkCore.Proxies 包,并通过调用 UseLazyLoadingProxies 来启用该包。 例如:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseLazyLoadingProxies()
        .UseOscar(myConnectionString);

或在使用 AddDbContext 时:

.AddDbContext<BloggingContext>(b => b.UseLazyLoadingProxies().UseOscar(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; }
}

更多内容请关注EFCore 开发文档

加载相关数据

异步查询

异步查询避免在数据库中执行查询时阻塞线程。 异步查询对于在厚客户机应用程序中保持响应UI非常重要。 它们还可以提高Web应用程序的吞吐量,从而释放线程来服务Web应用程序中的其他请求。

Entity Framework Core 提供一组类似于 LINQ 方法的异步扩展方法,用于执行查询并返回结果。 示例包括 ToListAsync()、ToArrayAsync()、SingleAsync()。 某些 LINQ 运算符(如 Where(...) 或 OrderBy(...))没有对应的异步版本,因为这些方法仅用于构建 LINQ 表达式树,而不会导致在数据库中执行查询。

public async Task<List<Blog>> GetBlogsAsync()
{
    using (var context = new BloggingContext())
    {
        var first = await context.Blogs.FirstOrDefaultAsync();
        return await context.Blogs.ToListAsync();
    }
}

原生 SQL 查询

通过 Entity Framework Core 可以在使用关系数据库时下降到原始 SQL 查询。 所需查询不能使用 LINQ 来表示时,可以使用原始 SQL 查询。 如果使用 LINQ 查询导致 SQL 查询效率低下,也可以使用原始 SQL 查询。 原始 SQL 查询可返回一般实体类型或者模型中的无键实体类型.

可使用 FromSqlRaw 扩展方法基于原始 SQL 查询开始 LINQ 查询。 FromSqlRaw 只能在直接位于 DbSet<> 上的查询根上使用。

var blogs = context.Blogs
    .FromSqlRaw("SELECT * FROM dbo.Blogs")
    .ToList();

原生 SQL 查询可用于执行存储过程。

var blogs = context.Blogs
    .FromSqlRaw("EXECUTE dbo.GetMostPopularBlogs")
    .ToList();

传递参数

var user = "johndoe";
var blogs = context.Blogs
    .FromSqlRaw("EXECUTE dbo.GetMostPopularBlogsForUser {0}", user)
    .ToList();
注意:
始终对原始 SQL 查询使用参数化
向原始 SQL 查询引入任何用户提供的值时,必须注意防范 SQL 注入攻击。 除了验证确保此类值不包含无效字符,请始终使用会将值与 SQL 文本分开发送的参数化处理。
具体而言,如果连接和内插的字符串 ($"") 带有用户提供的未经验证的值,则切勿将其传递到 FromSqlRaw 或 ExecuteSqlRaw。 通过 FromSqlInterpolated 和 ExecuteSqlInterpolated 方法,可采用一种能抵御 SQL 注入攻击的方式使用字符串内插语法。
var user = "johndoe";
var blogs = context.Blogs
    .FromSqlRaw("EXECUTE dbo.GetMostPopularBlogsForUser {0}", user)
    .ToList();

更多内容请关注EFCore 开发文档

原生 SQL 查询

全局查询筛选器

全局查询筛选器是应用于元数据模型(通常为 OnModelCreating)中的实体类型的 LINQ 查询谓词(通常传递给 LINQ Where 查询运算符的布尔表达式)。 此类筛选器自动应用于涉及这些实体类型(包括通过使用 Include 或直接导航属性引用等方式间接引用的实体类型)的所有 LINQ 查询。 此功能的一些常见应用如下:

  • 软删除 - 实体类型定义“IsDeleted” 属性。
  • 多租户 - 实体类型定义“TenantId” 属性。

详细内容请查阅EFCore 开发文档

全局查询筛选器

查询标记

此功能有助于将代码中的 LINQ 查询与日志中捕获的已生成 SQL 查询相关联。 使用新增的 TagWith() 方法对 LINQ 查询进行批注:

var nearestFriends =
    (from f in context.Friends.TagWith("This is my spatial query!")
    orderby f.Location.Distance(myLocation) descending
    select f).Take(5).ToList();

详细内容请查阅EFCore 开发文档

查询标记