【问题标题】:How to build a collection filter via expression trees in c#如何通过 C# 中的表达式树构建集合过滤器
【发布时间】:2013-12-05 12:47:37
【问题描述】:

我正在为UserProfiles 构建一个基于已知属性但未知(直到运行时)过滤条件组合的过滤系统。

在我之前的问题How do I create a generic Expression that has an expression as a parameter 中,我想出了一种方法,可以为通过导航属性(即(User)u=&gt; u.NavigationProperty.AnotherNavigationProperty.SomeValue)从用户实体可访问的任何值属性设置 FilterDefinition 我有一个方法可以为给定的属性、操作( > Expression<Func<User,bool>>。

现在是时候根据集合属性过滤它们了。 例如说用户有 CheckedOutBooks 集合(这完全是虚构的,但会做) 我需要为 User 对象上 CheckedOutBooks 集合的 Name 属性创建一个过滤器定义。

我有什么: 用户集合
用户类具有书籍集合
现在我想创建一个方法

Expression<Func<User,bool>> GetPredicate(Expression<User,TProperty>, Operations operation, TProperty value) 

我可以打电话给GetPredicate(u=&gt;u.Books.Select(b=&gt;b.Name), Operations.Contains, "C# in a nutshell")

并得到一个类似于

的表达式
u=>u.Books.Any(b=>b.Name == "C# in a nutshell")

我在想也许将第一个参数一分为二来实现这一点会更容易。 也许u=&gt;u.Booksb=&gt;b.Name 会做得更好?

编辑: 到目前为止我得到了什么:

  class FilterDefinitionForCollectionPropertyValues<T>:FilterDefinition, IUserFilter
    {


    public Expression<Func<UserProfile, IEnumerable<T>>> CollectionSelector { get; set; }
    public Expression<Func<T, string>> CollectionPropertySelector { get; set; }


    public Expression<Func<Profile.UserProfile, bool>> GetFilterPredicateFor(FilterOperations operation, string value)
    {
        var propertyParameter = CollectionPropertySelector.Parameters[0];
        var collectionParameter = CollectionSelector.Parameters[0];

// building predicate to supply to Enumerable.Any() method
        var left = CollectionPropertySelector.Body;
        var right = Expression.Constant(value);    
        var innerLambda = Expression.Equal(left, right);    
        Expression<Func<T, bool>> innerFunction = Expression.Lambda<Func<T, bool>>(innerLambda, propertyParameter);



        var method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(T));

        var outerLambda = Expression.Call(method, Expression.Property(collectionParameter, typeof(UserProfile).GetProperty("StaticSegments")), innerFunction);

        throw new NotImplementedException();

    }

    }

现在这个工作非常好,完全符合需要,现在我唯一需要弄清楚的是如何替换typeof(UserProfile).GetProperty("StaticSegments")) 以某种方式使用CollectionPropertySelector,在当前示例中将是(UserProfile)u=&gt;u.StaticSegments

【问题讨论】:

  • 您能否解释一下这种方法的原因?我可以想到很多方法来做到这一点,但我不知道哪一种对你有帮助,因为你没有说你想如何实际使用它。
  • 为什么还需要GetPredicate()u =&gt; u.Books.Select(b=&gt;b.Name).Contains("C# in a nutshell") 或类似的东西不可以吗?
  • @Luaan 我已经更新了问题以包含一些背景故事。从我现在的立场来看,没有办法使用动态 linq。并且定义所有可能的过滤组合的可能组合似乎很乏味而且不是 DRY (-:
  • @svick 当然它会做临时的,但会有一个用户界面,用户可以选择如何塑造过滤器。用户所关心的只是一个变量、条件和一个要匹配的值。所以应该有一个变量列表,可能的比较操作和一些关于值可能是什么的提示。
  • 这是一个想法——你真的需要一个表达式树吗? IQueryable 还不够吗?你仍然可以使用它来查询数据库(或其他任何东西),它有大量方便的方法来做你需要的事情,并且构建查询变得更加容易。

标签: c# filtering expression-trees


【解决方案1】:

你快完成了。现在您只需要做一个小技巧 - 将您的 CollectionPropertySelector lambda 表达式包装在 CollectionSelector lambda 表达式中。

Expression<Func<TParent,bool>> Wrap<TParent,TElement>(Expression<Func<TParent, IEnumerable<TElement>>> collection, Expression<Func<TElement, bool>> isOne, Expression<Func<IEnumerable<TElement>, Func<TElement, bool>, bool>> isAny)
{
    var parent = Expression.Parameter(typeof(TParent), "parent");

    return 
        (Expression<Func<TParent, bool>>)Expression.Lambda
        (
            Expression.Invoke
            (
                isAny,
                Expression.Invoke
                (
                    collection,
                    parent
                ),
                isOne
            ),
            parent
        );
}

您可能需要稍微更改一下以用于您的特定场景,但这个想法应该很清楚。我的测试基本上是这样的:

var user = new User { Books = new List<string> { "Book 1", "Book 2" }};

var query = Wrap<User, string>(u => u.Books, b => b.Contains("Bookx"), (collection, condition) => collection.Any(condition));

所以你指定了集合选择器、谓词和谓词运算符,你就完成了。

为了清晰起见,我将其编写为通用方法,但它是动态的,本质上不是强类型的,因此如果需要,应该很容易将其更改为非通用方法。

【讨论】:

  • 我有我的解决方案,将在星期一发布。感谢您的参与。
【解决方案2】:

好的,我自己解决了。 我将其发布到 gitHub:
https://github.com/Alexander-Taran/Lambda-Magic-Filters

给定过滤器定义类(目前还没有重构以支持字符串以外的属性,但稍后会做):

class FilterDefinitionForCollectionPropertyValues<T>:FilterDefinition, IUserFilter
{

//This guy just points to a collection property
public Expression<Func<UserProfile, IEnumerable<T>>> CollectionSelector { get; set; }
// This one points to a property of a member of that collection.
public Expression<Func<T, string>> CollectionPropertySelector { get; set; }


//This one does the heavy work of building a predicate based on a collection,   
//it's member property, operation type and a valaue
public System.Linq.Expressions.Expression<Func<Profile.UserProfile, bool>> GetFilterPredicateFor(FilterOperations operation, string value)
{
    var getExpressionBody = CollectionPropertySelector.Body as MemberExpression;
    if (getExpressionBody == null)
    {
        throw new Exception("getExpressionBody is not MemberExpression: " + CollectionPropertySelector.Body);
    }

    var propertyParameter = CollectionPropertySelector.Parameters[0];
    var collectionParameter = CollectionSelector.Parameters[0];
    var left = CollectionPropertySelector.Body;
    var right = Expression.Constant(value);

    // this is so far hardcoded, but might be changed later based on operation type  
    // as well as a "method" below
    var innerLambda = Expression.Equal(left, right);

    Expression<Func<T, bool>> innerFunction = Expression.Lambda<Func<T, bool>>(innerLambda, propertyParameter);
    // this is hadrcoded again, but maybe changed later when other type of operation will be needed
    var method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(T));

    var outerLambda = Expression.Call(method, Expression.Property(collectionParameter, (CollectionSelector.Body as MemberExpression).Member as System.Reflection.PropertyInfo), innerFunction);

    var result = Expression.Lambda<Func<UserProfile, bool>>(outerLambda, collectionParameter);

    return result;

}

}

【讨论】:

    猜你喜欢
    • 2012-02-17
    • 2016-11-25
    • 2017-09-24
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 2023-04-08
    • 2020-03-28
    • 1970-01-01
    相关资源
    最近更新 更多