问题
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