如何使用反射获取属性并在查询中使用它?

3

我有一个通用方法,并且我想在我的方法中添加搜索功能。作为参数,我会得到要搜索的属性名称(string)和要在列表中搜索的值(string)。我该如何实现这一功能?

这段代码不是我所拥有的确切代码,因此可能看起来我可以使用其他选项,例如Expression函数,但在我的情况下不可能,因为它应该在Api控制器中使用。 在真实项目中,我使用了单位工作和库模式,为了简单起见,我试图将它们添加到一个简单的函数中。

public async Task<ActionResult<List<T>>> GetAll(string? filterProperty = null, string? filterValue = null)
{
    IQueryable<T> query = dbSet;
    if (filterProperty != null)
    {
        PropertyInfo property = typeof(T).GetProperty(filterProperty);
        query = query. Where(u=> u.property.Contains(filterValue));
    }
    return await query.ToListAsync();
}
1个回答

4
对于 IQueryable,您需要为筛选谓词创建一个 LambdaExpression。(对于 IEnumerable,您可以将该表达式编译成适当的 Func<>。) 所有这些都通过构建表示所需操作的表达式树来实现。在本例中,您正在调用Contains,传递过滤值的常量给获取属性值的结果。 让我们从Contains方法开始,您可以重复使用它。不是基本反射,而是以下是如何使用表达式获取它:
static readonly MethodInfo _contains =
  (((Expression<Func<string, bool>>)(s => s.Contains("a"))).Body as MethodCallExpression)
  .Method;

虽然这看起来有点令人困惑,但我们正在利用编译器来为我们完成反射工作。有时这比查找具有多个重载版本的方法的正确版本或者当不清楚涉及哪个扩展方法时更容易。结果是在这里_contains被初始化为我们需要的方法信息。

您已经拥有目标属性的属性信息,现在让我们将它们组合起来:

// The parameter for the predicate
var row = Expression.Parameter(typeof(T), "row");

// Constant for the filter value
var filter = Expression.Constant(filterValue);

// Get the value of the property
var prop = Expression.Property(property);

// Call 'Contains' on the property value
var body = Expression.Call(prop, _contains, filter);

// Finally, generate the lambda
var predicate = Expression.Lambda<Func<T, bool>(body, row);

// Apply to the query
query = query.Where(predicate);

或者以更紧凑的形式表达:

var row = Expression.Parameter(typeof(T), "row");
var predicate = 
    Expression.Lambda<Func<T, bool>
    (
        Expression.Call
        (
            Expression.Property(row, property),
            _contains,
            Expression.Constant(filterValue)
        ),
        row
    );

当你使用 IEnumerable<T> 处理数据时,predicate.Compile() 会生成一个可用的 Func<T, bool>,以传递给 IEnumerable.Where()

private static readonly MethodInfo _tostring = typeof(Object).GetMethod("ToString");
static readonly MethodInfo _compare = (((Expression<Func<string, bool>>)(s => s.Contains(""))).Body as MethodCallExpression).Method;

public static IEnumerable<T> Search<T>(this IEnumerable<T> items, string propertyName, string filterValue)
{
    var property = typeof(T).GetProperty(propertyName);
    var row = Expression.Parameter(typeof(T), "row");
    
    // Let's make sure we're actually dealing with a string here
    Expression prop = Expression.Property(row, property);
    if (property.PropertyType != typeof(string))
        prop = Expression.Call(prop, _tostring);
    
    var func = 
        Expression.Lambda<Func<T, bool>>
        (
            Expression.Call
            (
                prop,
                _compare,
                Expression.Constant(filterValue)
            ),
            row
        ).Dump().Compile();

    return items.Where(func);
}

表达式非常灵活,有很多场合可以派上用场。组合函数并多次调用它可能比一直进行反射更有效率,还可以通过合并表达式等方式实现有趣的功能。


这是完美的代码,对我来说就像一门课程。我喜欢使用扩展方法的想法,但我在使用它时遇到了两个问题:首先,_tostring字段的类型未被提及,代码显示错误。我尝试过MethodInfo,但它显示其他错误。其次,使用IEnumerable<T>的扩展方法是否具有内存效率,因为我们应该先获取数据,然后根据输出进行过滤,难道我们不能只是使用IQueryable<T>的扩展方法吗? - Ashkan Khalaj
错误信息为:Error CS0029 在此行代码中无法隐式转换类型'System.Linq.Expressions.MethodCallExpression'到 'System.Linq.Expressions.MemberExpression': prop = Expression.Call(prop, _tostring); 且无法进行显式转换。 - Ashkan Khalaj
@AshkanKhalaj 是的,我匆忙输入了那个,抱歉。MethodCallExpressionMemberExpression都是从Expression派生出来的,所以显式地为变量指定类型是可以的。 - Corey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接