具有方法 'Select' 的动态表达式树
Dynamic expression tree with method 'Select'
我正在尝试使用表达式树构建以下 lambda 表达式 ->
info => info.event_objects.Select(x => x.object_info.contact_info)
我在 Whosebug 上进行了大量研究并找到了一些答案。
This one 帮助我构建了
info =>
info.event_objects.Any(x => x.object_info.contact_info.someBool == true)
如你所见,方法'Any'很容易搞定
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any"
&& m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);
主要问题出在方法 'Select' 上。如果您尝试将名称 "Any" 更改为 "Select",则会出现以下异常:
var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name ==
"Select" && m.GetParameters().Length == 2);
selectMethod = selectMethod.MakeGenericMethod(childType);
Additional information: Sequence contains more than one matching element
我试过的另一种方法:
MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name
== "Select"))
foreach (ParameterInfo p in m.GetParameters().Where(p =>
p.Name.Equals("selector")))
if (p.ParameterType.GetGenericArguments().Count() == 2)
selectMethod = (MethodInfo)p.Member;
看起来可行,但后来我在这里遇到异常:
navigationPropertyPredicate = Expression.Call(selectMethod, parameter,
navigationPropertyPredicate);
Additional information: Method
System.Collections.Generic.IEnumerable`1[TResult] Select[TSource,TResult]
(System.Collections.Generic.IEnumerable`1[TSource],
System.Func`2[TSource,TResult]) is a generic method definition>
在那之后,我尝试使用:
selectMethod = selectMethod.MakeGenericMethod(typeof(event_objects),
typeof(contact_info));
其实也没什么用。
这是我的完整代码
public static Expression GetNavigationPropertyExpression(Expression parameter, params string[] properties)
{
Expression resultExpression = null;
Expression childParameter, navigationPropertyPredicate;
Type childType = null;
if (properties.Count() > 1)
{
//build path
parameter = Expression.Property(parameter, properties[0]);
var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
//if it´s a collection we later need to use the predicate in the methodexpressioncall
if (isCollection)
{
childType = parameter.Type.GetGenericArguments()[0];
childParameter = Expression.Parameter(childType, "x");
}
else
{
childParameter = parameter;
}
//skip current property and get navigation property expression recursivly
var innerProperties = properties.Skip(1).ToArray();
navigationPropertyPredicate = GetNavigationPropertyExpression(childParameter, innerProperties);
if (isCollection)
{
//var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Select" && m.GetParameters().Length == 2);
//selectMethod = selectMethod.MakeGenericMethod(childType);
MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name == "Select"))
foreach (ParameterInfo p in m.GetParameters().Where(p => p.Name.Equals("selector")))
if (p.ParameterType.GetGenericArguments().Count() == 2)
selectMethod = (MethodInfo)p.Member;
navigationPropertyPredicate = Expression.Call(selectMethod, parameter, navigationPropertyPredicate);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
else
{
resultExpression = navigationPropertyPredicate;
}
}
else
{
var childProperty = parameter.Type.GetProperty(properties[0]);
var left = Expression.Property(parameter, childProperty);
var right = Expression.Constant(true, typeof(bool));
navigationPropertyPredicate = Expression.Lambda(left);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
return resultExpression;
}
private static Expression MakeLambda(Expression parameter, Expression predicate)
{
var resultParameterVisitor = new ParameterVisitor();
resultParameterVisitor.Visit(parameter);
var resultParameter = resultParameterVisitor.Parameter;
return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
}
private class ParameterVisitor : ExpressionVisitor
{
public Expression Parameter
{
get;
private set;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Parameter = node;
return node;
}
}
[TestMethod]
public void TestDynamicExpression()
{
var parameter = Expression.Parameter(typeof(event_info), "x");
var expression = GetNavigationPropertyExpression(parameter, "event_objects", "object_info", "contact_info");
}
编辑:不幸的是,我尝试了 问题的答案,但它似乎不起作用
"Additional information: Sequence contains more than one matching element"
与 "Any()" 不同,"Select()" 有两个带有两个参数的重载:
Select<TS, TR>(IE<TS> source, Func<TS, TR> selector)
Select<TS, TR>(IE<TS> source, Func<TS, int, TR> selector)
(采用“(项目,索引)=>”选择器 lambda)
由于您的代码已经依赖于 "esoteric knowledge",所以只取其中的第一个:
var selectMethod = typeof(Enumerable).GetMethods()
.First(m => m.Name == nameof(Enumerable.Select)
&& m.GetParameters().Length == 2);
您可以通过使用两个 Expression.Call
方法重载之一(一个用于 static
和一个用于 instance
方法)接受 string methodName
和 Type[] typeArguments
.
此外,由于表达式和 lambda 表达式构建没有明确区分,当前的实现过于复杂并且包含其他问题。
这是一个正确的工作实现:
public static LambdaExpression GetNavigationPropertySelector(Type type, params string[] properties)
{
return GetNavigationPropertySelector(type, properties, 0);
}
private static LambdaExpression GetNavigationPropertySelector(Type type, string[] properties, int depth)
{
var parameter = Expression.Parameter(type, depth == 0 ? "x" : "x" + depth);
var body = GetNavigationPropertyExpression(parameter, properties, depth);
return Expression.Lambda(body, parameter);
}
private static Expression GetNavigationPropertyExpression(Expression source, string[] properties, int depth)
{
if (depth >= properties.Length)
return source;
var property = Expression.Property(source, properties[depth]);
if (typeof(IEnumerable).IsAssignableFrom(property.Type))
{
var elementType = property.Type.GetGenericArguments()[0];
var elementSelector = GetNavigationPropertySelector(elementType, properties, depth + 1);
return Expression.Call(
typeof(Enumerable), "Select", new Type[] { elementType, elementSelector.Body.Type },
property, elementSelector);
}
else
{
return GetNavigationPropertyExpression(property, properties, depth + 1);
}
}
首先是public方法。它在内部使用接下来的两个私有方法递归构建所需的 lambda。如您所见,我区分了构建 lambda 表达式和仅用作 lambda 主体的表达式。
测试:
var selector = GetNavigationPropertySelector(typeof(event_info),
"event_objects", "object_info", "contact_info");
结果:
x => x.event_objects.Select(x1 => x1.object_info.contact_info)
我正在尝试使用表达式树构建以下 lambda 表达式 ->
info => info.event_objects.Select(x => x.object_info.contact_info)
我在 Whosebug 上进行了大量研究并找到了一些答案。 This one 帮助我构建了
info =>
info.event_objects.Any(x => x.object_info.contact_info.someBool == true)
如你所见,方法'Any'很容易搞定
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any"
&& m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);
主要问题出在方法 'Select' 上。如果您尝试将名称 "Any" 更改为 "Select",则会出现以下异常:
var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name ==
"Select" && m.GetParameters().Length == 2);
selectMethod = selectMethod.MakeGenericMethod(childType);
Additional information: Sequence contains more than one matching element
我试过的另一种方法:
MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name
== "Select"))
foreach (ParameterInfo p in m.GetParameters().Where(p =>
p.Name.Equals("selector")))
if (p.ParameterType.GetGenericArguments().Count() == 2)
selectMethod = (MethodInfo)p.Member;
看起来可行,但后来我在这里遇到异常:
navigationPropertyPredicate = Expression.Call(selectMethod, parameter,
navigationPropertyPredicate);
Additional information: Method
System.Collections.Generic.IEnumerable`1[TResult] Select[TSource,TResult]
(System.Collections.Generic.IEnumerable`1[TSource],
System.Func`2[TSource,TResult]) is a generic method definition>
在那之后,我尝试使用:
selectMethod = selectMethod.MakeGenericMethod(typeof(event_objects),
typeof(contact_info));
其实也没什么用。
这是我的完整代码
public static Expression GetNavigationPropertyExpression(Expression parameter, params string[] properties)
{
Expression resultExpression = null;
Expression childParameter, navigationPropertyPredicate;
Type childType = null;
if (properties.Count() > 1)
{
//build path
parameter = Expression.Property(parameter, properties[0]);
var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
//if it´s a collection we later need to use the predicate in the methodexpressioncall
if (isCollection)
{
childType = parameter.Type.GetGenericArguments()[0];
childParameter = Expression.Parameter(childType, "x");
}
else
{
childParameter = parameter;
}
//skip current property and get navigation property expression recursivly
var innerProperties = properties.Skip(1).ToArray();
navigationPropertyPredicate = GetNavigationPropertyExpression(childParameter, innerProperties);
if (isCollection)
{
//var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Select" && m.GetParameters().Length == 2);
//selectMethod = selectMethod.MakeGenericMethod(childType);
MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name == "Select"))
foreach (ParameterInfo p in m.GetParameters().Where(p => p.Name.Equals("selector")))
if (p.ParameterType.GetGenericArguments().Count() == 2)
selectMethod = (MethodInfo)p.Member;
navigationPropertyPredicate = Expression.Call(selectMethod, parameter, navigationPropertyPredicate);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
else
{
resultExpression = navigationPropertyPredicate;
}
}
else
{
var childProperty = parameter.Type.GetProperty(properties[0]);
var left = Expression.Property(parameter, childProperty);
var right = Expression.Constant(true, typeof(bool));
navigationPropertyPredicate = Expression.Lambda(left);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
return resultExpression;
}
private static Expression MakeLambda(Expression parameter, Expression predicate)
{
var resultParameterVisitor = new ParameterVisitor();
resultParameterVisitor.Visit(parameter);
var resultParameter = resultParameterVisitor.Parameter;
return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
}
private class ParameterVisitor : ExpressionVisitor
{
public Expression Parameter
{
get;
private set;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Parameter = node;
return node;
}
}
[TestMethod]
public void TestDynamicExpression()
{
var parameter = Expression.Parameter(typeof(event_info), "x");
var expression = GetNavigationPropertyExpression(parameter, "event_objects", "object_info", "contact_info");
}
编辑:不幸的是,我尝试了
"Additional information: Sequence contains more than one matching element"
与 "Any()" 不同,"Select()" 有两个带有两个参数的重载:
Select<TS, TR>(IE<TS> source, Func<TS, TR> selector)
Select<TS, TR>(IE<TS> source, Func<TS, int, TR> selector)
(采用“(项目,索引)=>”选择器 lambda)
由于您的代码已经依赖于 "esoteric knowledge",所以只取其中的第一个:
var selectMethod = typeof(Enumerable).GetMethods()
.First(m => m.Name == nameof(Enumerable.Select)
&& m.GetParameters().Length == 2);
您可以通过使用两个 Expression.Call
方法重载之一(一个用于 static
和一个用于 instance
方法)接受 string methodName
和 Type[] typeArguments
.
此外,由于表达式和 lambda 表达式构建没有明确区分,当前的实现过于复杂并且包含其他问题。
这是一个正确的工作实现:
public static LambdaExpression GetNavigationPropertySelector(Type type, params string[] properties)
{
return GetNavigationPropertySelector(type, properties, 0);
}
private static LambdaExpression GetNavigationPropertySelector(Type type, string[] properties, int depth)
{
var parameter = Expression.Parameter(type, depth == 0 ? "x" : "x" + depth);
var body = GetNavigationPropertyExpression(parameter, properties, depth);
return Expression.Lambda(body, parameter);
}
private static Expression GetNavigationPropertyExpression(Expression source, string[] properties, int depth)
{
if (depth >= properties.Length)
return source;
var property = Expression.Property(source, properties[depth]);
if (typeof(IEnumerable).IsAssignableFrom(property.Type))
{
var elementType = property.Type.GetGenericArguments()[0];
var elementSelector = GetNavigationPropertySelector(elementType, properties, depth + 1);
return Expression.Call(
typeof(Enumerable), "Select", new Type[] { elementType, elementSelector.Body.Type },
property, elementSelector);
}
else
{
return GetNavigationPropertyExpression(property, properties, depth + 1);
}
}
首先是public方法。它在内部使用接下来的两个私有方法递归构建所需的 lambda。如您所见,我区分了构建 lambda 表达式和仅用作 lambda 主体的表达式。
测试:
var selector = GetNavigationPropertySelector(typeof(event_info),
"event_objects", "object_info", "contact_info");
结果:
x => x.event_objects.Select(x1 => x1.object_info.contact_info)