动态创建用于选择对象属性的表达式

本文关键字:属性 表达式 对象 选择 创建 用于 动态 | 更新日期: 2023-09-27 18:34:16

我希望能够动态地构建一个表达式,它本质上是一个属性选择器。

我正在尝试使用它,以便我可以提供一个灵活的搜索 UI,然后将选定的搜索参数转换为实体框架查询。

由于我正在使用的另一个库,我拥有了我需要的大部分东西,但缺少将我的查询字符串参数转换为其他库所需的适当表达式选择器的最后一部分。

该库采用以下参数:

Expression<Func<TObject, TPropertyType>>

如果将其烘焙到应用程序中,将如何编码的示例是:

Expression<Func<MyObject, int>> expression = x=> x.IntegerProperty;

但是,我需要能够动态生成此表达式,因为重要的一点是,我所知道的只是对象的类型(MyObject(和作为字符串值的属性名称("IntegerProperty"(。属性值显然将映射到对象上的属性,该属性可以是任何非复杂类型。

所以从本质上讲,我认为我想找到一种方法来动态构建表达式,该表达式指定要返回的正确对象属性以及返回值由该属性类型确定的位置。

伪代码 :

string ObjectPropertyName
Type ObjectType
Type ObjectPropertyType = typeof(ObjectType).GetProperty(ObjectPropertyName).Property
 Expression<Func<[ObjectType], [ObjectPropertyType]>> expression = x=> x.[ObjectPropertyName];

更新:

我已经走到了这一步

ParameterExpression objectParameter = Expression.Parameter(type, "x");
MemberExpression objectProperty = Expression.Property(objectParameter, "PropertyNameString");
Expression<Func<ObjectType, int>> expression = Expression.Lambda<Func<ObjectType, int>>(objectProperty, objectParameter);

但是我遇到的问题是返回类型并不总是 int,而可能是其他类型。

动态创建用于选择对象属性的表达式

做你要求的事情有点棘手,但并非不可能。由于属性类型在运行时之前是未知的,因此您无法声明Expression<Func<,>>因此将通过反射来完成。

public static class QueryableExtension
{
    public static object Build<Tobject>(this Tobject source, string propertyName)
    {
        var propInfo = typeof(Tobject).GetProperty(propertyName);
        var parameter = Expression.Parameter(typeof(Tobject), "x");
        var property = Expression.Property(parameter, propInfo);
        var delegateType = typeof(Func<,>)
                           .MakeGenericType(typeof(Tobject), propInfo.PropertyType);
        var lambda = GetExpressionLambdaMethod()
                        .MakeGenericMethod(delegateType)
                        .Invoke(null, new object[] { property, new[] { parameter } });
        return lambda;
    }
    public static MethodInfo GetExpressionLambdaMethod()
    {
       return typeof(Expression)
                     .GetMethods()
                     .Where(m => m.Name == "Lambda")
                     .Select(m => new
                     {
                         Method = m,
                         Params = m.GetParameters(),
                         Args = m.GetGenericArguments()
                     })
                     .Where(x => x.Params.Length == 2
                                 && x.Args.Length == 1
                                 )
                     .Select(x => x.Method)
                     .First();
    }
}

用法-

var expression = testObject.Build("YourPropertyName");

现在,这将使用返回类型的属性构建所需的表达式。但是由于我们不知道你的库,但我建议你通过反射调用你的库方法,并传递包装在对象下的表达式。

正如我在评论中提到的,在不知道属性类型的情况下构建表达式很容易(即使有嵌套属性支持(:

static LambdaExpression MakeSelector(Type objectType, string path)
{
    var item = Expression.Parameter(objectType, "item");
    var body = path.Split('.').Aggregate((Expression)item, Expression.PropertyOrField);
    return Expression.Lambda(body, item);
}

但是,您需要找到一种方法来调用泛型库方法 - 使用反射或动态调用。

如果同时将ObjectTypeObjectPropertyType作为泛型类型参数,则可以使用 Expression 类执行以下操作:

public static Expression<Func<TObject, TPropertyType>> Generate<TObject, TPropertyType>(
    string property_name)
{
    var parameter = Expression.Parameter(typeof (TObject));
    return Expression.Lambda<Func<TObject, TPropertyType>>(
        Expression.Property(parameter, property_name), parameter);
}

有旧的 intresting 库 DynamicLinq。也许它会对你有用。它扩展了System.Linq.Dynamic以支持执行字符串中定义的Lambda表达式。通过使用DynamicLinq,你可以做一些这样的思考:

   public class IndexViewModel
   {
      public bool HasPassword { get; set; }
      public string PhoneNumber { get; set; }
      public bool TwoFactor { get; set; }
      public bool BrowserRemembered { get; set; }
    }
    //...........
    Expression<Func<IndexViewModel, bool>> ex =
    System.Linq.Dynamic.DynamicExpression.ParseLambda<IndexViewModel, bool>("TwoFactor");
        var model = new ReactJs.NET.Models.IndexViewModel() { TwoFactor = true };
        var res = ex.Compile()(model);
        // res == true
        System.Diagnostics.Debug.Assert(res);