如何为 创建表达式树.其中(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()部分时,我会遇到更多的麻烦,所以任何帮助也将是伟大的,谢谢。

如何为 创建表达式树.其中(x => x.<深度属性>.选择(y => y.id).相交(列表<int>).任意())

经过深思熟虑、调查和尝试,我想出了一个解决方案。

首先,我制作了一个更简单的目标查询版本(我在问题中使用的静态示例(,因此而不是:

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();
                 }
            );

但是我对你想解决的问题的细节了解不够,无法肯定地说。