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!
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
- Visual Studio 11 beta: le novità di ASP.NET MVC 4 e ASP.NET Web Pages 2.0, il 2 marzo 2012 alle 10:03
- WPC 2011: Cosa mi/ci/vi aspetta!, il 14 novembre 2011 alle 08:01
- CDays10, the day after: ringraziamenti, precisazioni, tricchettracc e bombamman, il 18 dicembre 2010 alle 13:17
- ASP.NET Web Forms ai Community Days, nel 2010, il 10 dicembre 2010 alle 09:23
- Inside ModelVirtualCasting #7: Come ti creo una form MVC in 20 secondi (a dire tanto...), il 15 giugno 2010 alle 07:50