Entity Framework中ThenInclude()的参数化实现方案问询
EF Core ThenInclude 参数化扩展实现与重复 Include 的性能问题
一、实现支持类型检查的 ThenInclude 参数化扩展
既然已经搞定了Include的参数化扩展,要给ThenInclude做类似功能,核心是封装主Include表达式和后续链式ThenInclude表达式链,同时保留编译时类型检查,避免运行时出错。
1. 定义嵌套包含的描述类
先创建一个泛型类,用来打包一组Include + N个ThenInclude的逻辑,确保类型严格匹配:
public class IncludeChain<TEntity, TPrevProp> { // 初始的Include表达式 public Expression<Func<TEntity, TPrevProp>> IncludeExpr { get; } // 后续的ThenInclude表达式列表 public IEnumerable<Expression<Func<TPrevProp, object>>> ThenIncludeExprs { get; } public IncludeChain(Expression<Func<TEntity, TPrevProp>> includeExpr, params Expression<Func<TPrevProp, object>>[] thenIncludeExprs) { IncludeExpr = includeExpr; ThenIncludeExprs = thenIncludeExprs; } }
2. 实现Queryable扩展方法
接下来写扩展方法,遍历每个IncludeChain,先执行Include,再链式跑所有ThenInclude。这里要注意区分集合类型和引用类型的ThenInclude重载(EF Core针对两种关联类型提供了不同的方法):
using Microsoft.EntityFrameworkCore; using System.Linq.Expressions; using System.Reflection; public static class QueryableIncludeExtensions { public static IQueryable<TEntity> IncludeWithChains<TEntity>(this IQueryable<TEntity> query, params IncludeChain<TEntity, object>[] includeChains) where TEntity : class { foreach (var chain in includeChains) { // 先执行初始Include var currentQuery = query.Include(chain.IncludeExpr); var prevPropType = chain.IncludeExpr.Body.Type; foreach (var thenExpr in chain.ThenIncludeExprs) { // 判断当前关联属性是集合还是引用类型 bool isCollection = typeof(IEnumerable).IsAssignableFrom(prevPropType) && prevPropType != typeof(string); MethodInfo thenIncludeMethod; if (isCollection) { // 集合类型的ThenInclude:需要三个泛型参数(主实体、集合元素类型、目标属性类型) var elementType = prevPropType.GetGenericArguments()[0]; thenIncludeMethod = typeof(EntityFrameworkQueryableExtensions) .GetMethods(BindingFlags.Public | BindingFlags.Static) .First(m => m.Name == nameof(EntityFrameworkQueryableExtensions.ThenInclude) && m.GetGenericArguments().Length == 3 && m.GetParameters().Length == 2) .MakeGenericMethod(typeof(TEntity), elementType, thenExpr.Body.Type); } else { // 引用类型的ThenInclude:需要两个泛型参数(主实体、引用属性类型、目标属性类型) thenIncludeMethod = typeof(EntityFrameworkQueryableExtensions) .GetMethods(BindingFlags.Public | BindingFlags.Static) .First(m => m.Name == nameof(EntityFrameworkQueryableExtensions.ThenInclude) && m.GetGenericArguments().Length == 2 && m.GetParameters().Length == 2) .MakeGenericMethod(typeof(TEntity), prevPropType, thenExpr.Body.Type); } // 反射调用ThenInclude,更新查询对象 currentQuery = (IQueryable<TEntity>)thenIncludeMethod.Invoke(null, new object[] { currentQuery, thenExpr }); // 更新下一个ThenInclude的前置属性类型 prevPropType = thenExpr.Body.Type; } query = currentQuery; } return query; } }
3. 使用示例
可以把常用的ThenInclude链封装到扩展类里,方便复用:
public static class CampaignIncludeExtensions { // 封装Campaign的关联加载链 public static Expression<Func<Campaign, object>>[] WithRelated => new[] { c => c.Owner, c => c.Category.ParentCategory }; }
然后在查询中调用:
// 加载AppUser的Campaigns,以及Campaign的Owner和Category.ParentCategory;同时加载AppUser的Profile和Address var users = dbContext.AppUsers .IncludeWithChains( new IncludeChain<AppUser, IEnumerable<Campaign>>(u => u.Campaigns, CampaignIncludeExtensions.WithRelated), new IncludeChain<AppUser, Profile>(u => u.Profile, p => p.Address) ) .ToList();
这种写法完全保留编译时类型检查,写错属性名会直接报错,同时避免了重复编写冗长的Include/ThenInclude链。
二、重复调用相同Include的性能影响
结论:不会影响SQL性能
- EF Core层面:EF Core的查询构建器会自动去重重复的
Include调用。比如连续两次写Include(u => u.Campaigns),EF只会在最终生成的SQL中添加一次关联JOIN,不会重复生成。 - SQL Server层面:即使EF不小心生成了重复的JOIN(这种情况几乎不会发生),SQL Server的查询优化器也会识别出重复的关联逻辑,自动优化掉多余的JOIN操作,不会对执行性能产生影响。
所以不用担心重复Include会导致SQL性能下降,EF和SQL Server都会处理这种情况。
内容的提问来源于stack exchange,提问作者David Thielen
相关产品推荐
相关产品推荐

