简体   繁体   English

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

[英]How to insert an existing IQueryable element to a ThenInclude method in EF Core

How do I insert an existing IQueryable element in a ThenInclude method?如何在ThenInclude方法中插入现有的IQueryable元素?

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

Interesting question.有趣的问题。

The problem is that Include / ThenInclude chain is not composable.问题是Include / ThenInclude链不可组合。 In theory the chain can be extracted from the IQueryable expression and then Include to be transformed to ThenInclude .理论上,可以从IQueryable表达式中提取链,然后将Include转换为ThenInclude

But that's not enough.但这还不够。 All these calls return IIncludableQueryable<TEntity, TProperty> , where the TEntity is from the original IQueryable .所有这些调用都返回IIncludableQueryable<TEntity, TProperty> ,其中TEntity来自原始IQueryable Hence the ThneInclude calls also need to be remapped.因此ThneInclude调用也需要重新映射。

Another problem is when the includable chain contains multiple Include calls.另一个问题是当包含链包含多个Include调用时。 Every Include except the first "restarts" the chain, hence should apply the original chain before converting it to ThenInclude .每个Include除了第一个“重新启动”链,因此应该在将其转换为ThenInclude之前应用原始链。

With that being said, following is a sample implementation which does that:话虽如此,以下是执行此操作的示例实现:

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

The two custom ThenInclude method overloads are to support both reference and collection navigation properties (similar to the standart ThenInclude overloads).两个自定义ThenInclude方法重载是为了支持引用和集合导航属性(类似于标准的ThenInclude重载)。

Now your sample will compile and will insert the second query includes into the the first query include chain.现在您的示例将编译并将第二个查询包含插入到第一个查询包含链中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM