有趣的问题。
问题是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
过载)。
现在您的示例将编译并将第二个查询包含插入到第一个查询包含链中。