Creare un filtro custom per QueryExtender

di Marco De Sanctis, in ASP.NET,

QueryExtender è uno strumento molto comodo per modificare le query generate da una EntityDataSource e ciò che lo rende secondo me parecchio potente è la sua espandibilità.

Creare filtri personalizzati è infatti parecchio semplice se si ha un po' di dimistichezza con le lambda expression, visto che, in fin dei conti, non dobbiamo far altro che generare l'espressione desiderata e applicarla alla sorgente. Vediamo un po' nel dettaglio come fare, ad esempio, a realizzare un filtro in cui possiamo specificare il tipo di relazione (">", ">=", "<", ecc.) e il termine di comparazione.

Il primo passo è quello di realizzare una classe che erediti da DataSourceExpression o meglio, dato che il nostro filtro dovrà essere parametrico, da ParameterDataSourceExpression, che implementa già tutta la logica necessaria a recuperare i valori dei parametri.

L'unico metodo da realizzare è GetQueryable, che riceve in ingresso un oggetto IQueryable e lo restituisce dopo averne eventualmente modificato l'espressione:

public override IQueryable GetQueryable(IQueryable source)
{
  // recupero i parametri grazie alla logica della classe base
  var values = this.Parameters.GetValues(this.Context, this.Owner);
  
  // se non tutti i parametri sono specificati, non faccio nulla
  if (values.Count != 2 || values[0] == null || values[1] == null)
    return null;
    
  // .. qui logica per calcolare il filtro ..
}

L'aspetto, se vogliamo, leggermente complesso, è costruire la LambdaExpression, dato che richiede un po' di conoscenza dell'anatomia di questo tipo di oggetti. Immaginiamo di voler scrivere questo tipo di espressione sull'oggetto Product e cerchiamo di capire come scomporla:

p => p.Amount < 25

Essa contiene:

1) Una costante "25"

var constant = Expression.Constant(25, typeof(int));

2) Un parametro "p" di tipo Product

var parameterExpression = Expression.Parameter(typeof(Product));

3) L'accesso alla proprietà "Amount" di Product

var propertyExpression = Expression.PropertyOrField(parameterExpression, "Amount");

4) Il confronto "<" tra il risultato del punto (3) e quello del punto (1)

var compare = Expression.LessThan(propertyExpression, constant);

Questo oggetto è il body della nostra lambda expression, ossia tutto ciò che è a destra del simbolo "=>". Per ottenere l'espressione completa è sufficiente specificare quali sono i parametri che essa dovrà gestire, ossia i membri a sinistra del simbolo "=>" (nel nostro caso Product, e quindi la stessa parameterExpression del punto 2):

var lambda = Expression.Lambda(compare, new ParameterExpression[] { parameterExpression });

Mettendo tutto insieme, il metodo GetQueryable diviene:

public override IQueryable GetQueryable(IQueryable source)
{
    var values = this.Parameters.GetValues(this.Context, this.Owner);


    if (values.Count != 2 || values[0] == null || values[1] == null)
        return null;

    // recupero la property su cui effettuare il filtro
    var parameterExpression = 
      Expression.Parameter(source.ElementType);
    var propertyExpression = 
      Expression.PropertyOrField(parameterExpression, this.DataField);

    // creo il secondo membro dell'expression in base al valore recuperato
    TypeConverter converter = 
      TypeDescriptor.GetConverter(propertyExpression.Type);
    var constant = 
      Expression.Constant(converter.ConvertFrom(values[1]), propertyExpression.Type);

    // creo l'espressione di confronto
    var comparisonType = getComparisonType((string)values[0]);
    var compare = 
      buildCompareExpression(propertyExpression, comparisonType, constant);

    // genero la lambdaexpression
    var lambda = 
      Expression.Lambda(compare, new ParameterExpression[] { parameterExpression });

    return source.Provider.CreateQuery(
      Expression.Call(
        typeof(Queryable), "Where", 
               new Type[] { source.ElementType }, 
               new Expression[] { source.Expression, Expression.Quote(lambda) }));
}

Alcune cose da notare:

  • L'uso di un TypeConverter per convertire il parametro nel tipo desiderato da Expression.Constant;
  • Il metodo buildCompareExpression non fa altro che valutare l'operatore selezionato dall'utente ("<", ">", ecc.) e costruire l'espressione corrispondente ("Expression.LessThan", "Expression.GreaterThan")
  • L'uso di Expression.Call e di IQueryProvider.CreateQuery per aggiungere una condizione di Where alla IQueryable originale. Questo è necessario perché l'extension method Where è generico e quindi richiede di specificare il tipo, che però nel nostro caso non è noto a priori.
  • Il fatto che abbiamo realizzato un filtro Where è un qualcosa di strettamente correlato alla nostra particolare implementazione, visto che non esistono vincoli infrastrutturali in tal senso. Morale della favola: nulla ci vieta di implementare una DataSourceExpression per realizzare una Group By.

Il codice dell'esempio è scaricabile qui, per farlo funzionare vi basta sostituire la connection string con una che punti al database Northwind.

Ciao!

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese