如何将现有的IQueryable元素插入EF Core中的ThenInclude方法 [英] How to insert an existing IQueryable element to a ThenInclude method in EF Core

查看:265
本文介绍了如何将现有的IQueryable元素插入EF Core中的ThenInclude方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何在 ThenInclude 方法中插入现有的 IQueryable 元素?

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

推荐答案

有趣的问题.

问题是 Include / ThenInclude 链不可组合.从理论上讲,可以从 IQueryable 表达式中提取链,然后从 Include 转换为 ThenInclude .

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.

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

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.

另一个问题是当包含的链包含多个 Include 调用时.除第一个重新启动"链外的每个 Include ,因此应在将原始链转换为 ThenInclude 之前应用原始链.

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

这两个自定义的 ThenInclude 方法重载将同时支持引用和集合导航属性(类似于标准的 ThenInclude 重载).

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.

这篇关于如何将现有的IQueryable元素插入EF Core中的ThenInclude方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆