You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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性能

  1. EF Core层面:EF Core的查询构建器会自动去重重复的Include调用。比如连续两次写Include(u => u.Campaigns),EF只会在最终生成的SQL中添加一次关联JOIN,不会重复生成。
  2. SQL Server层面:即使EF不小心生成了重复的JOIN(这种情况几乎不会发生),SQL Server的查询优化器也会识别出重复的关联逻辑,自动优化掉多余的JOIN操作,不会对执行性能产生影响。

所以不用担心重复Include会导致SQL性能下降,EF和SQL Server都会处理这种情况。


内容的提问来源于stack exchange,提问作者David Thielen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 06:10:44