【Entity Framework】你必须要了解EF中数据查询之数据加载

简介: 【Entity Framework】你必须要了解EF中数据查询之数据加载


一、概述

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

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


二、预先加载

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


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

Entity Framework Core 会根据之前已加载到上下文实例中的实体自动填充导航属性。 因此,即使不显式包含导航属性的数据,如果先前加载了部分或所有关联实体,则仍可能填充该属性。

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


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

2.1 包含多个层级

使用ThenInclude方法可以依循关系包含多个层级的关联数据。以下示例加载了所有博客,其相关文章及每篇文章的作者。


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

可通过链式调用ThenInclude,进一步包含更深级别的关联数据。


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 => post.Photo)
        .Include(blog       => blog.Owner)
        .ThenInclude(owner  => owner.Photo)
        .ToList();
}

你可能希望将已包含的某个实体的多个关联实体都包含进来。 例如,当查询 Blogs 时,你会包含 Posts,然后希望同时包含 Posts 的 Author 和 Tags。 为了包含这两项内容,需要从根级别开始指定每个包含路径。 例如,Blog -> Posts -> Author 和 Blog -> Posts -> Tags。 这并不意味着会获得冗余联接查询,在大多数情况下,EF 会在生成 SQL 时合并相应的联接查询。


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

• 9

2.2 经过筛选的包含

在应用包含功能来加载相关数据时,可对已包含的集合导航应用某些可枚举的操作,这样就可以对结果进行筛选和排序。

支持的操作包括:WhereOrderByOrderByDescendingThenByThenByDescendingSkipTake

应对传递到Include方法的Lambda中的集合导航应用这类操作。如下例所示:


using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(
            blog => blog.Posts
                .Where(post => post.BlogId == 1)
                .OrderByDescending(post => post.Title)
                .Take(5))
        .ToList();
}

可对多次包含的每个导航应用相同的操作:


using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
        .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();
}

还可以通过执行返回关联实体的单独查询来显式加载导航属性。 如果已启用更改跟踪,则在查询具体化实体时,EF Core 将自动设置新加载的实体的导航属性以引用任何已加载的实体,并设置已加载实体的导航属性以引用新加载的实体。

3.1查询关联实体

可以获得表示导航属性内容的LINQ查询。

这使你可对查询应用其他运算符。示例:


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

还可以筛选要加载到内存中的关联实体。


using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);
    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()
        .UseSqlServer(myConnectionString);

或在使用 AddDbContext 时:


.AddDbContext<BloggingContext>(
    b => b.UseLazyLoadingProxies()
          .UseSqlServer(myConnectionString));

EF Core将为可被重写的任何导航属性启用延迟加载。


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

4.1 不使用代理进行延迟加载

不使用代理进行延迟加载的工作方式是将 ILazyLoader 注入到实体中,如实体类型构造函数中所述。例如:


public class Blog
{
    private ICollection<Post> _posts;

    public Blog()
    {
    }

    private Blog(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }

    public ICollection<Post> Posts
    {
        get => LazyLoader.Load(this, ref _posts);
        set => _posts = value;
    }
}

public class Post
{
    private Blog _blog;

    public Post()
    {
    }

    private Post(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog
    {
        get => LazyLoader.Load(this, ref _blog);
        set => _blog = value;
    }
}

此方法不要求实体类型为可继承的类型,也不要求导航属性必须是虚拟的,且允许通过new创建的实体实例在附加到上下文后可进行延迟加载。但它需要对Microsoft.EntityFrameworkCore.Abstractions包中定义的ILazyLoader服务的引用。此包包含所允许的最少的一组类型,以便将依赖此包时所产生的影响将到最低。不过,可以将ILazyLoader.Load方法以委托的形式注入,这样就可以完全避免依赖于实体类型的任何EF Core包。


public class Blog
{
    private ICollection<Post> _posts;

    public Blog()
    {
    }

    private Blog(Action<object, string> lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private Action<object, string> LazyLoader { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }

    public ICollection<Post> Posts
    {
        get => LazyLoader.Load(this, ref _posts);
        set => _posts = value;
    }
}

public class Post
{
    private Blog _blog;

    public Post()
    {
    }

    private Post(Action<object, string> lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private Action<object, string> LazyLoader { get; set; }

    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog
    {
        get => LazyLoader.Load(this, ref _blog);
        set => _blog = value;
    }
}


上述代码使用 Load 扩展方法,以便更干净地使用委托:


public static class PocoLoadingExtensions
{
    public static TRelated Load<TRelated>(
        this Action<object, string> loader,
        object entity,
        ref TRelated navigationField,
        [CallerMemberName] string navigationName = null)
        where TRelated : class
    {
        loader?.Invoke(entity, navigationName);

        return navigationField;
    }
}
目录
相关文章
|
4月前
|
SQL 存储 开发框架
Entity Framework Core 与 SQL Server 携手,高级查询技巧大揭秘!让你的数据操作更高效!
【8月更文挑战第31天】Entity Framework Core (EF Core) 是一个强大的对象关系映射(ORM)框架,尤其与 SQL Server 数据库结合使用时,提供了多种高级查询技巧,显著提升数据操作效率。它支持 LINQ 查询,使代码简洁易读;延迟加载与预先加载机制优化了相关实体的加载策略;通过 `FromSqlRaw` 或 `FromSqlInterpolated` 方法支持原始 SQL 查询;可调用存储过程执行复杂任务;利用 `Skip` 和 `Take` 实现分页查询,便于处理大量数据。这些特性共同提升了开发者的生产力和应用程序的性能。
194 0
|
4月前
|
存储 开发框架 .NET
提升数据处理效率:深入探索Entity Framework Core中的LINQ查询技巧与最佳实践
【8月更文挑战第31天】在现代 .NET 应用开发中,Entity Framework Core (EF Core) 是访问数据库的首选技术。本文通过在线商店案例,探讨 EF Core 中 LINQ 查询的最佳实践:使用 `Include` 加载相关数据,避免过早调用 `ToList()`,利用 `Select` 进行投影以优化性能,采用异步查询提升响应性,并考虑使用存储过程处理复杂查询。这些技巧有助于构建高效、可维护的数据访问层。
48 0
|
6月前
|
SQL 关系型数据库 数据库连接
详解 Entity Framework(EF)核心组件与数据访问方法探索
Entity Framework是一个ORM框架,简化.NET开发者与数据库的交互。它始于.NET Framework的一部分,但现在可通过NuGet独立获取。ORM允许对象模型直接映射到数据库结构,避免直接编写SQL。
332 2
详解 Entity Framework(EF)核心组件与数据访问方法探索
|
6月前
|
SQL 存储 开发框架
【Entity Framework】你必须了解的之自定义SQL查询
【Entity Framework】你必须了解的之自定义SQL查询
96 0
|
6月前
|
SQL 存储 开发框架
【Entity Framework】EF中的增删改查
【Entity Framework】EF中的增删改查
165 0
|
6月前
|
SQL 开发框架 .NET
【Entity Framework】聊一聊EF如何使用数据库函数
【Entity Framework】聊一聊EF如何使用数据库函数
91 0
|
6月前
|
SQL 开发框架 .NET
【Entity Framework】聊聊单个查询与拆分查询
【Entity Framework】聊聊单个查询与拆分查询
39 0
|
6月前
|
存储 SQL 开发框架
【Entity Framework】如何使用EF中的生成值
【Entity Framework】如何使用EF中的生成值
46 0
|
6月前
|
存储 开发框架 安全
【Entity Framework】闲话EF中批量配置
【Entity Framework】闲话EF中批量配置
43 0
|
6月前
|
开发框架 .NET 数据库
【Entity Framework】EF中SaveChanges如何使用
【Entity Framework】EF中SaveChanges如何使用
54 0