实体框架 linq 查询 Include() 多个子实体

Posted

技术标签:

【中文标题】实体框架 linq 查询 Include() 多个子实体【英文标题】:Entity framework linq query Include() multiple children entities 【发布时间】:2010-07-28 19:15:00 【问题描述】:

这可能是一个非常基本的问题,但是在编写跨越三个(或更多)级别的查询时,包含多个子实体的好方法是什么?

即我有 4 个表:CompanyEmployeeEmployee_CarEmployee_Country

公司与员工是一对一的关系。

Employee 与 Employee_Car 和 Employee_Country 都有 1:m 关系。

如果我想编写一个返回所有 4 个表的数据的查询,我目前正在编写:

Company company = context.Companies
                         .Include("Employee.Employee_Car")
                         .Include("Employee.Employee_Country")
                         .FirstOrDefault(c => c.Id == companyID);

必须有更优雅的方式!这是冗长的,并且会生成可怕的 SQL

我在 VS 2010 中使用 EF4

【问题讨论】:

【参考方案1】:

使用extension methods。 将 NameOfContext 替换为您的对象上下文的名称。

public static class Extensions
   public static IQueryable<Company> CompleteCompanies(this NameOfContext context)
         return context.Companies
             .Include("Employee.Employee_Car")
             .Include("Employee.Employee_Country") ;
     

     public static Company CompanyById(this NameOfContext context, int companyID)
         return context.Companies
             .Include("Employee.Employee_Car")
             .Include("Employee.Employee_Country")
             .FirstOrDefault(c => c.Id == companyID) ;
      


那么你的代码就变成了

     Company company = 
          context.CompleteCompanies().FirstOrDefault(c => c.Id == companyID);

     //or if you want even more
     Company company = 
          context.CompanyById(companyID);

【讨论】:

但我想这样使用它://inside public static class Extensions public static IQueryable&lt;Company&gt; CompleteCompanies(this DbSet&lt;Company&gt; table) return table .Include("Employee.Employee_Car") .Include("Employee.Employee_Country") ; //code will be... Company company = context.Companies.CompleteCompanies().FirstOrDefault(c =&gt; c.Id == companyID); //same for next advanced method Bullsye Nix。扩展应该是……嗯……扩展预定义功能的第一个调用端口。 多年后,我不会推荐基于字符串的包含,因为它们不是运行时安全的。如果导航属性名称发生更改或拼写错误,它将中断。强烈建议改用类型化的包含。 自从引入 nameof(class) 以来,可以安全地使用这种方法。如果实体名称发生变化,它将在编译期间被拾取。示例:context.Companies.Include(nameof(Employee)) 如果需要更进一步,名称必须与 nameof(Employee)+"."+nameof(Employee_Car) 连接 扩展方法技术不适用于已编译的查询(至少在 EFCore 上不适用):github.com/aspnet/EntityFrameworkCore/issues/7016【参考方案2】:

EF 4.1 到 EF 6

有一个strongly typed .Include 允许通过将 Select 表达式提供到适当的深度来指定所需的急切加载深度:

using System.Data.Entity; // NB!

var company = context.Companies
                     .Include(co => co.Employees.Select(emp => emp.Employee_Car))
                     .Include(co => co.Employees.Select(emp => emp.Employee_Country))
                     .FirstOrDefault(co => co.companyID == companyID);

生成的 Sql 绝不是直观的,但似乎足够高效。我在GitHub here上放了一个小例子

EF 核心

EF Core 有一个新的扩展方法.ThenInclude(),虽然语法是slightly different:

var company = context.Companies
                     .Include(co => co.Employees)
                           .ThenInclude(emp => emp.Employee_Car)
                     .Include(co => co.Employees)
                           .ThenInclude(emp => emp.Employee_Country)

附上一些笔记

如上所述(Employees.Employee_CarEmployees.Employee_Country),如果您需要包含中间子集合的 2 个或更多子属性,you'll need to repeat.Include 为集合的每个子集合导航。 As per the docs,我会在 .ThenInclude 中保留额外的“缩进”以保持您的理智。

【讨论】:

我想知道如何使用强类型的 .Include 语句来做到这一点。用 Select 投影孩子就是答案! 我的“co.Employees.Select(...)”在“Select”上显示语法错误,说“‘Employees’不包含‘Select’[或扩展名的定义方法]”。我已经包含了 System.Data.Entity。我只想从连接表中获取一列。 我有一个父表两次引用同一个子表。使用旧的字符串包含语法很难预加载正确的关系。这种方式更加具体。请记住为强类型包含包含命名空间 System.Data.Entity。 使用 .net core 2.1 我需要命名空间 Microsoft.EntityFrameworkCore 而不是 System.Data.Entity【参考方案3】:

您可能会在codeplex.com 找到这篇感兴趣的文章。

Improving Entity Framework Query Performance Using Graph-Based Querying。

本文介绍了一种以声明性图形形状的形式表达跨多个表的查询的新方法。

此外,本文还对这种新方法与 EF 查询进行了全面的性能比较。该分析表明 GBQ 的性能很快优于 EF 查询。

【讨论】:

如何在实际应用中实现? @Victor.Uduak 确实如此。虽然这是一种有趣的方法,但它与问题无关,而且远不是我可以直接放入我的 EF 应用程序的东西 @Victor.Udua,Auspex 我对这个问题的回答已经超过 10 年了。那时,涉及多个子实体的复杂查询由于不是强类型且性能较差而变得困难。我提到了另一种方法(GraphBasedQuerying)。在引用的文章中,我展示了如何使用强类型查询,并进行了广泛的性能比较。当时,该软件在 CodePlex 上可用。 github.com/Omar007/GraphBasedQuerying上有一个后续项目,你可以使用。我希望你意识到 11 年前我的回答是相关的 :-)【参考方案4】:

How do you construct a LINQ to Entities query to load child objects directly, instead of calling a Reference property or Load()

没有其他方法 - 除了实现延迟加载。

或者手动加载....

myobj = context.MyObjects.First();
myobj.ChildA.Load();
myobj.ChildB.Load();
...

【讨论】:

这不是被问到的问题。【参考方案5】:

可能会帮助某人,每个级别有 4 个级别和 2 个孩子

Library.Include(a => a.Library.Select(b => b.Library.Select(c => c.Library)))
            .Include(d=>d.Book.)
            .Include(g => g.Library.Select(h=>g.Book))
            .Include(j => j.Library.Select(k => k.Library.Select(l=>l.Book)))

【讨论】:

【参考方案6】:

这样做:

namespace Application.Test

    using Utils.Extensions;
    public class Test
    
        public DbSet<User> Users  get; set; 
        public DbSet<Room> Rooms  get; set; 
        public DbSet<Post> Posts  get; set; 
        public DbSet<Comment> Comments  get; set; 
        
        public void Foo()
        
            DB.Users.Include(x => x.Posts, x => x.Rooms, x => x.Members);
            //OR
            DB.Users.Include(x => x.Posts, x => x.Rooms, x => x.Members)
                .ThenInclude(x => x.Posts, y => y.Owner, y => y.Comments);
        
    

这个扩展可能会有所帮助:

namespace Utils.Extensions

    using Microsoft.EntityFrameworkCore;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    public static partial class LinqExtension
    
        public static IQueryable<TEntity> Include<TEntity>(
            this IQueryable<TEntity> sources,
            params Expression<Func<TEntity, object>>[] properties)
            where TEntity : class
        
            System.Text.RegularExpressions.Regex regex = new(@"^\w+[.]");
            IQueryable<TEntity> _sources = sources;
            foreach (var property in properties)
                _sources = _sources.Include($"regex.Replace(property.Body.ToString(), "")");
            return _sources;
        

        public static IQueryable<TEntity> ThenInclude<TEntity, TProperty>(
            this IQueryable<TEntity> sources,
            Expression<Func<TEntity, IEnumerable<TProperty>>> predicate,
            params Expression<Func<TProperty, object>>[] properties)
            where TEntity : class
        
            System.Text.RegularExpressions.Regex regex = new(@"^\w+[.]");
            IQueryable<TEntity> _sources = sources;
            foreach (var property in properties)
                _sources = _sources.Include($"regex.Replace(predicate.Body.ToString(), "").regex.Replace(property.Body.ToString(), "")");
            return _sources;
        
    

【讨论】:

以上是关于实体框架 linq 查询 Include() 多个子实体的主要内容,如果未能解决你的问题,请参考以下文章

查询 (LINQ) 表达式无法转换为实体框架核心

实体框架 + LINQ 缓慢与字符串查询速度?

实体框架Linq查询:如何在多个导航属性上从何处选择并从第三个导航属性中选择

优化多个 LINQ to Entity Framework 查询

实体框架,LINQ 查询

LINQ 查询子句的顺序是不是会影响实体框架的性能?