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

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

即,我有4个表:CompanyEmployeeEmployee_CarEmployee_Country

公司与员工的关系是1:m。

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

我使用EF4与VS 2010

362497 次浏览

你如何构造一个LINQ到实体的查询来直接加载子对象,而不是调用Reference属性或load ()

没有其他方法——除了实现惰性加载。

或手动加载....

myobj = context.MyObjects.First();
myobj.ChildA.Load();
myobj.ChildB.Load();
...
< p > 扩展方法使用。 将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);

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

本文提出了一种以声明性图形状的形式表示跨越多个表的查询的新方法。

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

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在这里上放了一个小例子

英孚的核心

EF Core有了一个新的扩展方法.ThenInclude(),尽管语法是稍微不同的:

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中保留额外的“缩进”,以保持您的理智。

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

这样做:

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