如何将现有 IQueryable 元素插入到 EF Core 中的 thenInclude 方法

2023-12-08

如何插入现有的IQueryable中的元素ThenInclude method?

public IQueryable<Store> Store => GetDbSet<Store>()
        .Include(st => st.App)
            .ThenInclude(app => app.Client)
                .ThenInclude(cl => cl.Country)
                    .ThenInclude(co => co.Culture)
        .Include(st => st.Features)
            .ThenInclude(this.StoreFeatures);

public IQueryable<StoreFeatures> StoreFeatures => GetDbSet<StoreFeatures>()
        .Include(ft => ft.Cultures)
            .ThenInclude(ct => ct.Culture);

有趣的问题。

问题是Include / ThenInclude链不可组合。理论上可以从IQueryable表达然后Include转变为ThenInclude.

但这还不够。所有这些调用都会返回IIncludableQueryable<TEntity, TProperty>,其中TEntity是来自原著的IQueryable。因此ThneInclude呼叫也需要重新映射。

另一个问题是当可包含链包含多个时Include来电。每一个Include除了第一个“重新启动”链之外,因此应在将其转换为之前应用原始链ThenInclude.

话虽如此,以下是执行此操作的示例实现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;

namespace Microsoft.EntityFrameworkCore
{
    public static class IncludeExtensions
    {
        const string IncludeMethodName = nameof(EntityFrameworkQueryableExtensions.Include);
        const string ThenIncludeMethodName = nameof(EntityFrameworkQueryableExtensions.ThenInclude);

        public static IQueryable<TEntity> ThenInclude<TEntity, TProperty>(
            this IIncludableQueryable<TEntity, IEnumerable<TProperty>> source,
            IQueryable<TProperty> includes) => source.Include(includes);

        public static IQueryable<TEntity> ThenInclude<TEntity, TProperty>(
            this IIncludableQueryable<TEntity, TProperty> source,
            IQueryable<TProperty> includes) => source.Include(includes);

        static IQueryable<TEntity> Include<TEntity, TProperty>(
            this IQueryable<TEntity> source, IQueryable<TProperty> includes)
        {
            var targetChain = GetIncludeChain(includes.Expression);
            if (targetChain.Count == 0) return source;
            var sourceChain = GetIncludeChain(source.Expression);
            var result = source.Expression;
            foreach (var targetInclude in targetChain)
            {
                bool isInclude = targetInclude.Method.Name == IncludeMethodName;
                if (isInclude && result != source.Expression)
                {
                    result = sourceChain.Aggregate(result, (r, i) =>
                        Expression.Call(i.Method, r, i.Arguments[1]));
                }
                var typeArgs = targetInclude.Method.GetGenericArguments();
                var prevPropertyType = isInclude ? typeof(TProperty) : typeArgs[1];
                var propertyType = typeArgs[isInclude ? 1 : 2];
                result = Expression.Call(
                    typeof(EntityFrameworkQueryableExtensions), ThenIncludeMethodName,
                    new[] { typeof(TEntity), prevPropertyType, propertyType },
                    result, targetInclude.Arguments[1]);
            }
            return source.Provider.CreateQuery<TEntity>(result);
        }

        static Stack<MethodCallExpression> GetIncludeChain(Expression source)
        {
            var result = new Stack<MethodCallExpression>();
            while (source is MethodCallExpression methodCall && methodCall.IsIncludeOrThenInclude())
            {
                result.Push(methodCall);
                source = methodCall.Arguments[0];
            }
            return result;
        }

        static bool IsIncludeOrThenInclude(this MethodCallExpression source)
            => source.Method.DeclaringType == typeof(EntityFrameworkQueryableExtensions)
                && source.Method.IsGenericMethod
                && (source.Method.Name == IncludeMethodName || source.Method.Name == ThenIncludeMethodName);
    }
}

两个定制ThenInclude方法重载是为了支持引用和集合导航属性(类似于标准ThenInclude过载)。

现在您的示例将编译并将第二个查询包含插入到第一个查询包含链中。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何将现有 IQueryable 元素插入到 EF Core 中的 thenInclude 方法 的相关文章

随机推荐