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

血红的双手。 提交于 2020-12-26 08:42:06

问题


How do I insert an existing IQueryable element in a 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);

回答1:


Interesting question.

The problem is that Include / ThenInclude chain is not composable. In theory the chain can be extracted from the IQueryable expression and then Include to be transformed to ThenInclude.

But that's not enough. All these calls return IIncludableQueryable<TEntity, TProperty>, where the TEntity is from the original IQueryable. Hence the ThneInclude calls also need to be remapped.

Another problem is when the includable chain contains multiple Include calls. Every Include except the first "restarts" the chain, hence should apply the original chain before converting it to 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).

Now your sample will compile and will insert the second query includes into the the first query include chain.



来源:https://stackoverflow.com/questions/62368932/how-to-insert-an-existing-iqueryable-element-to-a-theninclude-method-in-ef-core

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!