IQueryable: динамическое создание фильтрации OR

c# expression-trees iqueryable linq linq-to-entities

Вопрос

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

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

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

Прямо сейчас, я делаю это:

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

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


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

На основе решения IlyaBuiluk @ 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 );

}

Принятый ответ

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

Автор: Raúl Roa


Популярные ответы

Если у вас есть фиксированный набор операторов и фиксированный набор элементов, вы можете написать это почти без непосредственного обращения к деревьям выражений. Идея заключается в создании простых лямбда-выражений для различных фрагментов кода (например, 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);

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



Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему
Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему