如何为 创建表达式树.其中(x => x.<深度属性>.选择(y => y.id).相交(列表).任意())
本文关键字:id 任意 int 选择 列表 相交 深度 表达式 其中 创建 属性 | 更新日期: 2023-09-27 18:33:23
我正在创建一个方法,该方法接收一个Queryable<T>
源,一个带有属性名称/路径的字符串(例如,可以是深层属性"TrParent.DataTypes"
以实现此x => x.TrParent.DataTypes
(和Enumerable<int>
,其中包含我需要相交的值。
基本上,我来自动态创建以下查询的需要(我的意思是<DT_Det_Tr>
并且TrParent.DataTypes
仅在运行时知道,在示例中DT_Det_Tr
不是一个类型,它是一个类(:
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
请记住,前面的查询只是我需要动态实现的示例,我真正需要的是一个表达式树,它创建一个类似于上面所示的谓词,但使用动态类型并在字符串中指定的深度导航属性。
因此,我使用此函数为深层属性创建表达式:
private static LambdaExpression CreateDelegateExpression<T>(out Type resultingtype, string property, string parameterName = "x")
{
var type = typeof(T);
ParameterExpression param = Expression.Parameter(type, parameterName);
Expression expr = param;
foreach (string prop in property.Split('.'))
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
resultingtype = type;
return lambda;
}
以下是我到目前为止对我的函数所拥有的:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
//List of ids
var _value = Expression.Constant(value);
//Get delegate expression to the deep property and it's inner type
Type type = null;
var lambda = CreateDelegateExpression<T>(out type, property, "x");
var enumtype = type.GetGenericArguments()[0];
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
MethodInfo innermethod = typeof(Queryable).GetMethods().Where(x => x.Name == "Select").First();
//Error on next line...
var selectCall = Expression.Call(typeof(Queryable),
"Select",
new Type[] { enumtype, typeof(long) },
lambda,
propExp);
//TODO: Add rest of logic and actually filter the source
return source;
}
在var selectCall =
行中,我收到错误:
类型"System.Linq.Queryable"上没有泛型方法"选择"与提供的类型参数和参数兼容。如果该方法是非泛型的,则不应提供类型参数。
我在SO和其他网站上读了很多书,但我无法通过这一部分,我觉得当我到达.Intersect(List<int>).Any()
部分时,我会遇到更多的麻烦,所以任何帮助也将是伟大的,谢谢。
经过深思熟虑、调查和尝试,我想出了一个解决方案。
首先,我制作了一个更简单的目标查询版本(我在问题中使用的静态示例(,因此而不是:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
我做了这个:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Any(y => _vals.Contains(y.Id))
);
这更容易转换为表达式(或者至少对我来说是这样(,因为它省略了 Select 调用。
我摆脱了用于创建深度导航属性表达式的方法,并在我的 Intersect 函数中简化了它,这是因为它正在做一些我在这里并不真正需要的工作,而且我需要访问我在里面使用的一些变量,然后我做了这个:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
var type = typeof(T);
var _value = Expression.Constant(value); //List of ids
//Declare parameter for outer lambda
ParameterExpression param = Expression.Parameter(type, "x");
//Outer Lambda
Expression expr = param;
foreach (string prop in property.Split('.')) //Dig for deep property
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
//Get deep property's type
var enumtype = type.GetGenericArguments()[0];
//Declare parameter for inner lambda
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
//Inner Collection lambda logic
//Property for inner lambda
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
//Contains method call .Contains(y.Id)
var containsMethodExp = Expression.Call(typeof(Enumerable), "Contains", new[] { propExp.Type }, _value, propExp);
//Create Expression<Func<enumtype, bool>>
var innerDelegateType = typeof(Func<,>).MakeGenericType(enumtype, typeof(bool));
//Create Inner lambda y => _vals.Contains(y.Id)
var innerFunction = Expression.Lambda(innerDelegateType, containsMethodExp, tpe);
//Get Any method info
var anyMethod = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(enumtype);
//Call Any with inner function .Any(y => _vals.Contains(y.Id))
var outerFunction = Expression.Call(anyMethod, expr, innerFunction);
//Call Where
MethodCallExpression whereCallExpression = Expression.Call
(
typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<T, bool>>(outerFunction, new ParameterExpression[] { param })
);
//Create and return query
return source.Provider.CreateQuery<T>(whereCallExpression);
}
我希望这可以帮助任何尝试开发类似解决方案的人。
使用表达式树一开始可能非常困难和令人沮丧,但一旦掌握了它,它就是一个非常强大的工具。
如果您有权访问 c# 4.0 中的 dynamic 关键字,则可以像这样解决此问题:
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where(obj => { dynamic x = obj;
return x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any();
}
);
但是我对你想解决的问题的细节了解不够,无法肯定地说。