Entity Framework的Linq查询Include()多个子实体。

207

这可能是一个非常基础的问题,但写一条涵盖三个或更多层级的查询时,如何优雅地包含多个子实体?

比如我有4张表:CompanyEmployeeEmployee_CarEmployee_Country

CompanyEmployee之间存在1:m的关系。

EmployeeEmployee_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。

6个回答

214

EF Core

对于多层关系的及时加载(例如,孙子或祖父母关系),其中中间关系是集合(即与原始“主题”具有1到多的关系),EF Core具有一个新的扩展方法.ThenInclude(),语法略有不同于旧版EF 4-6语法:

using Microsoft.EntityFrameworkCore;
...

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个或更多子属性,您需要重复每个子集合的.Include导航。
  • 个人建议在.ThenInclude中保留额外的缩进,以保持清晰。

对于与原始主题是1:1(或N:1)的中介者的序列化,也支持点语法,例如

var company = context.Companies
                     .Include(co => co.City.Country);

这在功能上等同于:
var company = context.Companies
                     .Include(co => co.City)
                          .ThenInclude(ci => ci.Country);

然而,在EFCore中,使用“Select”链接一个与主题为1:N的中间层的旧的EF4 / 6语法不受支持,即:
var company = context.Companies
                     .Include(co => co.Employee.Select(emp => emp.Address));

我将为您翻译关于编程的中文内容。以下是需要翻译的内容:

通常会导致类似于以下错误:

不支持'System.IntPtr'实例的序列化和反序列化

EF 4.1到EF 6

有一个强类型的.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这里放了一个小例子。

4
我在想如何使用强类型的.Include语句来做到这一点。使用Select投影子项就是答案! - user4668483
1
我的“co.Employees.Select(...)”等价物在“Select”上显示语法错误,说“'Employees'不包含'Select'的定义[或扩展方法]”。我已经包括了System.Data.Entity。我只想从连接表中获取单个列。 - Chris Walsh
1
我有一个父表格两次引用同一个子表格的情况。旧的字符串包含语法中预加载正确关系很困难。这种方式更加具体明确。请记得加入namespace System.Data.Entity以进行强类型包含。 - Karl
1
使用 .NET Core 2.1,我需要使用命名空间 Microsoft.EntityFrameworkCore 而不是 System.Data.Entity。 - denvercoder9

212

使用扩展方法。 用你的对象上下文的名称替换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);

但我想这样使用它:`//在public static class Extensions内 public static IQueryable<Company> CompleteCompanies(this DbSet<Company> table){ return table .Include("Employee.Employee_Car") .Include("Employee.Employee_Country") ; }//代码将是... Company company = context.Companies.CompleteCompanies().FirstOrDefault(c => c.Id == companyID);//下一个高级方法同理` - Hamid
Bullsye Nix。扩展应该是扩展预定义功能的首选。 - ComeIn
18
多年以后,我不会推荐使用基于字符串的Include,因为它们在运行时不安全。如果导航属性名称发生更改或拼写错误,代码将无法正常工作。强烈建议使用类型化的Include代替。 - Jeff Putz
2
自从 nameof(class) 的引入,使用这种方法就变得更加安全了。如果实体名称发生更改,它将在编译期间被捕获。例如:context.Companies.Include(nameof(Employee))。如果需要进一步深入,就必须将名称连接起来,如 nameof(Employee)+"."+nameof(Employee_Car)。 - Karl
扩展方法技术在编译查询中不起作用(至少在EFCore上不起作用),在此得到确认:https://github.com/aspnet/EntityFrameworkCore/issues/7016 - Dunge
我们可以在Include中添加条件吗?例如,Include(“TableName”,conditions)以加载Include中的特定项?我的表中有一个IsDeleted标志,我不想在Include中加载它们。 - Seyed Reza Dadrezaei

26

这怎么在实际应用中实现? - Victor.Uduak
@Victor.Uduak 确实如此。虽然这是一种有趣的方法,但与问题无关,并且远非我可以轻松应用于我的EF应用程序的东西。 - Auspex
@Victor.Udua,Auspex我的答案已经超过10年了。当时,涉及多个子实体的复杂查询因为没有强类型化而效率低下,难以处理。我提到了一种替代方法(基于图形的查询)。在引用的文章中,我展示了如何使用强类型化查询,并进行了广泛的性能比较。当时该软件可在CodePlex上获得。后来还有一个跟进项目,可以在https://github.com/Omar007/GraphBasedQuerying 上使用。希望你明白11年前我的答案是相关的 :-) - Merijn

3

这不是被问到的问题。 - Auspex

1
要做到这一点:
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;
        }
    }
}

1
也许会对某些人有所帮助,每个级别有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)))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接