IQueryable: creazione dinamica di un filtro OR

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

Domanda

Ho una serie di criteri di ricerca in questa forma:

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

E voglio interrogare tutti gli oggetti che corrispondono a uno qualsiasi di questi criteri.

In questo momento, lo faccio da:

  • costruire un'espressione per ciascuno dei criteri
  • concatenando ogni espressione usando un'espressione 'OR'
  • costruire un'espressione lambda contenente l'espressione concatenata
  • passando l'espressione lambda al metodo IQueryable <>. Where ()

Conoscete un metodo più semplice per filtrare dinamicamente una raccolta IQueryable usando OR consecutivi?


BONUS La nostra soluzione:

Basato sulla soluzione 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 );

}

Risposta accettata

In termini di prestazioni e facilità di implementazione, come è questo un approccio migliore rispetto all'utilizzo di Dynamic Query Library? Credo che in questo modo sia possibile controllare meglio l'output SQL dei propri alberi di espressione.

Di Raúl Roa


Risposta popolare

Se hai un set fisso di operatori e un set fisso di membri, puoi scrivere questo quasi senza occuparti direttamente degli alberi di espressione. L'idea è di creare espressioni lambda semplici per vari pezzi di codice (es. Expression<Func<Entity, string>> per leggere la proprietà di un membro e simili per gli operatori) e quindi semplicemente comporli per costruire un albero di espressioni. Ho descritto la soluzione qui . L'unico problema è che la composizione di espressioni non è direttamente supportata in C #, quindi è necessario un po 'di pre-elaborazione (vedere la sezione "utilità espandibili").

Quindi è possibile memorizzare le funzioni di base in un dizionario e selezionare quello giusto (o una combinazione di esse) in base a ciò che l'utente seleziona. Ad esempio qualcosa come:

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);

L'articolo contiene anche un esempio con la composizione dinamica di OR o AND. Non ho aggiornato il codice per un po ', quindi ha bisogno di un po' di lavoro, ma credo che il progetto LINQ KIT contenga anche una versione di questa idea.



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché