IQueryable: создание динамически ИЛИ фильтрации - PullRequest
3 голосов
/ 15 сентября 2010

У меня есть набор критериев поиска в этой форме:

 member  |  value  |  operator
 --------+---------+---------
 height  |   10    |    >
 height  |    2    |    < 
 name    |  Carl   |   ==

И я хочу запросить все объекты, которые соответствуют любому из этих критериев.

Сейчас явыполняя это:

  • построение выражения для каждого из критериев
  • конкатенация каждого выражения с использованием выражения "ИЛИ"
  • построение лямбда-выражения, содержащего конкатенированныеexpression
  • передача лямбда-выражения в метод IQueryable <>. Where ()

Знаете ли вы самый простой способ динамически фильтровать коллекцию IQueryable с использованием последовательного ИЛИ?


БОНУС Наше решение:

На основе решения ИльяБайлук @ CodeProject

// The structure used by the new extension method
public struct SearchCriteria
{
    public string Column;
    public object Value;
    public WhereOperation Operation;
}

// How to convert the rules structure to the search criteria structure
var searchCriterias = grid.Where.rules.Select(Rule => new SearchCriteria
  {
      Column = Rule.field,
      Operation =
          (WhereOperation)
          StringEnum.Parse(
              typeof (WhereOperation),
              Rule.op),
      Value = Rule.data
  }).ToArray();


// Usage:
query = query.WhereOr(searchCriterias);


// Implementation
public static IQueryable<T> WhereOr<T>( this IQueryable<T> Query, SearchCriteria [ ] Criterias )
{
    if( Criterias.Count( ) == 0 )
        return Query;

    LambdaExpression lambda;
    Expression resultCondition = null;

    // Create a member expression pointing to given column
    ParameterExpression parameter = Expression.Parameter( Query.ElementType, "p" );

    foreach( var searchCriteria in Criterias )
    {
        if( string.IsNullOrEmpty( searchCriteria.Column ) )
            continue;

        MemberExpression memberAccess = null;
        foreach( var property in searchCriteria.Column.Split( '.' ) )
            memberAccess = MemberExpression.Property
                ( memberAccess ?? ( parameter as Expression ), property );

        // Change the type of the parameter 'value'. it is necessary for comparisons (specially for booleans)
        ConstantExpression filter = Expression.Constant
            (
                Convert.ChangeType( searchCriteria.Value, memberAccess.Type )
            );

        //switch operation
        Expression condition = null;
        switch( searchCriteria.Operation )
        {
            //equal ==
            case WhereOperation.Equal:
                condition = Expression.Equal( memberAccess, filter );
                break;
            //not equal !=
            case WhereOperation.NotEqual:
                condition = Expression.NotEqual( memberAccess, filter );
                break;
            // Greater
            case WhereOperation.Greater:
                condition = Expression.GreaterThan( memberAccess, filter );
                break;
            // Greater or equal
            case WhereOperation.GreaterOrEqual:
                condition = Expression.GreaterThanOrEqual( memberAccess, filter );
                break;
            // Less
            case WhereOperation.Less:
                condition = Expression.LessThan( memberAccess, filter );
                break;
            // Less or equal
            case WhereOperation.LessEqual:
                condition = Expression.LessThanOrEqual( memberAccess, filter );
                break;
            //string.Contains()
            case WhereOperation.Contains:
                condition = Expression.Call( memberAccess,
                                            typeof( string ).GetMethod( "Contains" ),
                                            Expression.Constant( searchCriteria.Value ) );
                break;

            default:
                continue;
        }

        resultCondition = resultCondition != null ? Expression.Or( resultCondition, condition ): condition;
    }

    lambda = Expression.Lambda( resultCondition, parameter );

    MethodCallExpression result = Expression.Call(
               typeof( Queryable ), "Where",
               new [ ] { Query.ElementType },
               Query.Expression,
               lambda );

    return Query.Provider.CreateQuery&lt;T&gt;( result );

}

Ответы [ 2 ]

3 голосов
/ 15 сентября 2010

Если у вас есть фиксированный набор операторов и фиксированный набор членов, то вы можете написать это почти без непосредственного обращения к деревьям выражений.Идея состоит в том, чтобы создать простые лямбда-выражения для различных фрагментов кода (например, Expression<Func<Entity, string>> для чтения свойства элемента и аналогичные для операторов), а затем просто составить их для построения дерева выражений.Я описал решение здесь .Единственная проблема заключается в том, что составление выражений напрямую не поддерживается в C #, поэтому вам нужно немного предварительно обработать (см. Раздел о «расширяемых утилитах»).

Затем вы можете хранить основные функции в словаре.и выберите правильный (или их комбинацию) в зависимости от того, что выберет пользователь.Например, что-то вроде:

NorthwindDataContext db = new NorthwindDataContext();

// A query that tests whether a property 
// (specified by 'selector' matches a string value
var queryBuilder = Linq.Func
  ((Expression<Func<Customer, string>> selector, string val) =>
      from c in db.Customers.ToExpandable()
      where selector.Expand(c).IndexOf(val) != -1
      select c);

// Dictionary with supported members...
var dict = new Dictionary<string, Expression<Func<Customer, string>>> 
  { { "CompanyName", c => c.CompanyName },
    { "Country",     c => c.Country },
    { "ContactName", c => c.ContactName } };

// Ask user for a property name & value and Build the query
string field = Console.ReadLine();
string value = Console.ReadLine();
var q = queryBuilder(dict[field], value);

Статья также содержит пример с динамическим составлением условий ИЛИ или И.Некоторое время я не обновлял код, поэтому он требует некоторой работы, но я считаю, что проект LINQ KIT также содержит версию этой идеи.

0 голосов
/ 13 октября 2010

С точки зрения производительности и простоты реализации, как этот подход лучше, чем использование библиотеки динамических запросов?Я полагаю, что таким образом вы сможете лучше контролировать вывод своих деревьев выражений в SQL.

...