表达式树 - 克隆和更改方法调用、Lambda 的类型(参数、Return 类型、匿名类型等)
Expression Tree - Clone and Change Types of MethodCalls, Lambdas (Parameters, Return Types, Anonymous Types etc)
下面的 Linq 查询接收 entitySet 作为 T 类型(属于接口类型)。由于此接口类型,生成的表达式树无法转换为 Sql,因此在表达式树查询评估期间,我需要一种方法来转换所有 Lambda、MethodCalls(连同它们的参数、返回类型(可能是 AnonymousType_of_Interface) 到提供的具体类型。
IQueryable<T> GetMasterConfigsForMasterConfigExport<T>(IQueryable<T> entitySet, GetMasterConfigsForMasterConfigExportParams p)
{
var selectedMasterConfigIds = p.SelectedMasterConfigIds.IsNotBlank() ? p.SelectedMasterConfigIds.ConvertCSVToLong() : Enumerable.Empty<long>();
var query = from masterConfig in entitySet
join selectedMasterConfigId in (Repository.ConvertToBigIntTable(selectedMasterConfigIds, "selectedMasterConfigId") as IQueryable<ConvertCSVToBigIntTableResult>)
on masterConfig.Id equals selectedMasterConfigId.Id into selectedMasterConfigIdsRS
from selectedMasterConfigId in selectedMasterConfigIdsRS.DefaultIfEmpty()
where masterConfig.IsActive && selectedMasterConfigId == null
select masterConfig;
return query;
}
生成的表达式树看起来像这样,我想以某种方式将所有出现的 IMasterConfig 替换为其他提供的具体对象:
.Call System.Linq.Queryable.Count(.Call System.Linq.Queryable.Select(
.Call System.Linq.Queryable.Where(
.Call System.Linq.Queryable.SelectMany(
.Extension<Microsoft.EntityFrameworkCore.Query.QueryRootExpression>,
'(.Lambda #Lambda1<System.Func`2[IMasterConfig,System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ParsedBigInt]]>),
'(.Lambda #Lambda2<System.Func`3[IMasterConfig,Lw.Sys.Repository.ParsedBigInt,<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt]]>))
,
'(.Lambda #Lambda3<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],System.Boolean]>))
,
'(.Lambda #Lambda4<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],IMasterConfig]>))
)
.Lambda #Lambda1<System.Func`2[IMasterConfig,System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ParsedBigInt]]>(IMasterConfig $masterConfig)
{
.Call System.Linq.Queryable.DefaultIfEmpty(.Call System.Linq.Queryable.Where(
.Call $__p_0.ConvertCSVToBigIntTable(
$__List_1,
$__Delim_2),
'(.Lambda #Lambda5<System.Func`2[Lw.Sys.Repository.ParsedBigInt,System.Boolean]>)))
}
.Lambda #Lambda2<System.Func`3[IMasterConfig,Lw.Sys.Repository.ParsedBigInt,<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt]]>(
IMasterConfig $masterConfig,
Lw.Sys.Repository.ParsedBigInt $selectedMasterConfigId) {
.New <>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt](
$masterConfig,
$selectedMasterConfigId)
}
.Lambda #Lambda3<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],System.Boolean]>(<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt] $<>h__TransparentIdentifier0)
{
($<>h__TransparentIdentifier0.masterConfig).IsActive && $<>h__TransparentIdentifier0.selectedMasterConfigId == null
}
.Lambda #Lambda4<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],IMasterConfig]>(<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt] $<>h__TransparentIdentifier0)
{
$<>h__TransparentIdentifier0.masterConfig
}
.Lambda #Lambda5<System.Func`2[Lw.Sys.Repository.ParsedBigInt,System.Boolean]>(Lw.Sys.Repository.ParsedBigInt $selectedMasterConfigId)
{
$masterConfig.Id == $selectedMasterConfigId.Id
}
正如 @IvonStoev 引用的那样,使用以下代码解决了 EFCore6 中的问题:
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == nameof(Queryable.GroupJoin) && node.Arguments.Count == 5)
{
var outer = Visit(node.Arguments[0]);
var inner = Visit(node.Arguments[1]);
var outerKeySelector = Visit(node.Arguments[2]).UnwrapLambdaFromQuote();
var innerKeySelector = Visit(node.Arguments[3]).UnwrapLambdaFromQuote();
var resultSelector = Visit(node.Arguments[4]).UnwrapLambdaFromQuote();
var outerKey = outerKeySelector.Body.ReplaceParameter(outerKeySelector.Parameters[0], resultSelector.Parameters[0]);
var innerKey = innerKeySelector.Body;
var keyMatch = MatchKeys(outerKey, innerKey);
var innerQuery = Expression.Call(
typeof(Queryable), nameof(Queryable.Where), new[] { innerKeySelector.Parameters[0].Type },
inner, Expression.Lambda(keyMatch, innerKeySelector.Parameters));
var asEnumerableInnerQuery = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.AsEnumerable),
new Type[] { innerKeySelector.Parameters[0].Type }, innerQuery);
var resultTypes = resultSelector.Parameters.Select(p => p.Type).ToArray();
var tempProjectionType = typeof(Tuple<,>).MakeGenericType(resultTypes);
var tempProjection = Expression.New(
tempProjectionType.GetConstructor(resultTypes),
new Expression[] { resultSelector.Parameters[0], asEnumerableInnerQuery },
tempProjectionType.GetProperty("Item1"), tempProjectionType.GetProperty("Item2"));
var tempQuery = Expression.Call(
typeof(Queryable), nameof(Queryable.Select), new[] { outerKeySelector.Parameters[0].Type, tempProjectionType },
outer, Expression.Lambda(tempProjection, resultSelector.Parameters[0]));
var tempResult = Expression.Parameter(tempProjectionType, "p");
var projection = resultSelector.Body
.ReplaceParameter(resultSelector.Parameters[0], Expression.Property(tempResult, "Item1"))
.ReplaceParameter(resultSelector.Parameters[1], Expression.Property(tempResult, "Item2"));
var query = Expression.Call(
typeof(Queryable), nameof(Queryable.Select), new[] { tempProjectionType, projection.Type },
tempQuery, Expression.Lambda(projection, tempResult));
return query;
}
return base.VisitMethodCall(node);
}
下面的 Linq 查询接收 entitySet 作为 T 类型(属于接口类型)。由于此接口类型,生成的表达式树无法转换为 Sql,因此在表达式树查询评估期间,我需要一种方法来转换所有 Lambda、MethodCalls(连同它们的参数、返回类型(可能是 AnonymousType_of_Interface) 到提供的具体类型。
IQueryable<T> GetMasterConfigsForMasterConfigExport<T>(IQueryable<T> entitySet, GetMasterConfigsForMasterConfigExportParams p)
{
var selectedMasterConfigIds = p.SelectedMasterConfigIds.IsNotBlank() ? p.SelectedMasterConfigIds.ConvertCSVToLong() : Enumerable.Empty<long>();
var query = from masterConfig in entitySet
join selectedMasterConfigId in (Repository.ConvertToBigIntTable(selectedMasterConfigIds, "selectedMasterConfigId") as IQueryable<ConvertCSVToBigIntTableResult>)
on masterConfig.Id equals selectedMasterConfigId.Id into selectedMasterConfigIdsRS
from selectedMasterConfigId in selectedMasterConfigIdsRS.DefaultIfEmpty()
where masterConfig.IsActive && selectedMasterConfigId == null
select masterConfig;
return query;
}
生成的表达式树看起来像这样,我想以某种方式将所有出现的 IMasterConfig 替换为其他提供的具体对象:
.Call System.Linq.Queryable.Count(.Call System.Linq.Queryable.Select(
.Call System.Linq.Queryable.Where(
.Call System.Linq.Queryable.SelectMany(
.Extension<Microsoft.EntityFrameworkCore.Query.QueryRootExpression>,
'(.Lambda #Lambda1<System.Func`2[IMasterConfig,System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ParsedBigInt]]>),
'(.Lambda #Lambda2<System.Func`3[IMasterConfig,Lw.Sys.Repository.ParsedBigInt,<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt]]>))
,
'(.Lambda #Lambda3<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],System.Boolean]>))
,
'(.Lambda #Lambda4<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],IMasterConfig]>))
)
.Lambda #Lambda1<System.Func`2[IMasterConfig,System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ParsedBigInt]]>(IMasterConfig $masterConfig)
{
.Call System.Linq.Queryable.DefaultIfEmpty(.Call System.Linq.Queryable.Where(
.Call $__p_0.ConvertCSVToBigIntTable(
$__List_1,
$__Delim_2),
'(.Lambda #Lambda5<System.Func`2[Lw.Sys.Repository.ParsedBigInt,System.Boolean]>)))
}
.Lambda #Lambda2<System.Func`3[IMasterConfig,Lw.Sys.Repository.ParsedBigInt,<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt]]>(
IMasterConfig $masterConfig,
Lw.Sys.Repository.ParsedBigInt $selectedMasterConfigId) {
.New <>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt](
$masterConfig,
$selectedMasterConfigId)
}
.Lambda #Lambda3<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],System.Boolean]>(<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt] $<>h__TransparentIdentifier0)
{
($<>h__TransparentIdentifier0.masterConfig).IsActive && $<>h__TransparentIdentifier0.selectedMasterConfigId == null
}
.Lambda #Lambda4<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],IMasterConfig]>(<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt] $<>h__TransparentIdentifier0)
{
$<>h__TransparentIdentifier0.masterConfig
}
.Lambda #Lambda5<System.Func`2[Lw.Sys.Repository.ParsedBigInt,System.Boolean]>(Lw.Sys.Repository.ParsedBigInt $selectedMasterConfigId)
{
$masterConfig.Id == $selectedMasterConfigId.Id
}
正如 @IvonStoev 引用的那样,使用以下代码解决了 EFCore6 中的问题:
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == nameof(Queryable.GroupJoin) && node.Arguments.Count == 5)
{
var outer = Visit(node.Arguments[0]);
var inner = Visit(node.Arguments[1]);
var outerKeySelector = Visit(node.Arguments[2]).UnwrapLambdaFromQuote();
var innerKeySelector = Visit(node.Arguments[3]).UnwrapLambdaFromQuote();
var resultSelector = Visit(node.Arguments[4]).UnwrapLambdaFromQuote();
var outerKey = outerKeySelector.Body.ReplaceParameter(outerKeySelector.Parameters[0], resultSelector.Parameters[0]);
var innerKey = innerKeySelector.Body;
var keyMatch = MatchKeys(outerKey, innerKey);
var innerQuery = Expression.Call(
typeof(Queryable), nameof(Queryable.Where), new[] { innerKeySelector.Parameters[0].Type },
inner, Expression.Lambda(keyMatch, innerKeySelector.Parameters));
var asEnumerableInnerQuery = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.AsEnumerable),
new Type[] { innerKeySelector.Parameters[0].Type }, innerQuery);
var resultTypes = resultSelector.Parameters.Select(p => p.Type).ToArray();
var tempProjectionType = typeof(Tuple<,>).MakeGenericType(resultTypes);
var tempProjection = Expression.New(
tempProjectionType.GetConstructor(resultTypes),
new Expression[] { resultSelector.Parameters[0], asEnumerableInnerQuery },
tempProjectionType.GetProperty("Item1"), tempProjectionType.GetProperty("Item2"));
var tempQuery = Expression.Call(
typeof(Queryable), nameof(Queryable.Select), new[] { outerKeySelector.Parameters[0].Type, tempProjectionType },
outer, Expression.Lambda(tempProjection, resultSelector.Parameters[0]));
var tempResult = Expression.Parameter(tempProjectionType, "p");
var projection = resultSelector.Body
.ReplaceParameter(resultSelector.Parameters[0], Expression.Property(tempResult, "Item1"))
.ReplaceParameter(resultSelector.Parameters[1], Expression.Property(tempResult, "Item2"));
var query = Expression.Call(
typeof(Queryable), nameof(Queryable.Select), new[] { tempProjectionType, projection.Type },
tempQuery, Expression.Lambda(projection, tempResult));
return query;
}
return base.VisitMethodCall(node);
}