Poco tempo fa (e tutt'ora) mi sono trovato di fronte ad un'applicazione sviluppata con NHibernate che richiedeva moltissimi filtri per fare delle ricerche molto complesse.
Cercando un pò in giro sono arrivato a leggere questo articolo di Ayende che esponeva una soluzione molto elegante e funzionale per effettuare ricerche complesse con NHibernate.....ed a pensarci bene è un buon pattern anche senza l'uso di NH.
L'articolo lo trovate qui: http://www.ayende.com/Blog/archive/2006/12/07/7055.aspx
Il concetto di base è quello di crearsi un oggetto Finder che non fa altro che incapsulare tutti i dati che devono essere cercati. Questo oggetto viene poi passato ad un metodo che altro non fa che valutare le informazioni presenti nell'oggetto per poi costruire il criteria o l'hql necessario.
Tornando al mio applicativo, ho notato che c'erano dei casi in cui la semplice impostazione di una Property non mi bastava poichè dovevo legarci anche come quell'informazione doveva essere trattata.
Per esempio una Property cognome doveva essere corredata anche dall'informazione di dove cercare, ossia se il valore doveva essere *contenuto*, oppure *iniziare per* o *finire per*. Altro esempio erano i valori range, come controllare se alcune informazioni erano comprese tra la data e la data.
Proprio per questo ho esteso la soluzione di Ayende creandomi un paio di classi chiamate SearchableField e SearchableRange.
L'implementazione delle classi è:
//SEARCHABLEFIELD
public class SearchableField<T>
{
private T _search;
private FinderOperator _expression = FinderOperator.Equals;
public FinderOperator Expression
{
get { return _expression; }
set { _expression = value; }
}
public T Search
{
get { return _search; }
set { _search = value; }
}
public SearchableField(T search, FinderOperator expression)
{
this._search = search;
this._expression = expression;
}
}
//SEARCHABLERANGE
public class SearchableRange<T>
{
private SearchableField<T> _from;
private SearchableField<T> _to;
public SearchableField<T> From
{
get { return _from; }
set { _from = value; }
}
public SearchableField<T> To
{
get { return _to; }
set { _to = value; }
}
public SearchableRange(SearchableField<T> from, SearchableField<T> to)
{
this._from = from;
this._to = to;
}
}
Logicamente le due classi utilizzano i Generics così da poter essere riutilizzate a seconda del tipo da implementare.
Come noterete c'è anche un parametro di tipo FinderOperator che identifica quale comportamento di ricerca implementare.
public enum FinderOperator
{
Equals = 0,
NotEquals = 1,
Contains = 2,
GreaterThan = 3,
LessThan = 4,
GreaterOrEqualThan = 5,
LessOrEqualThan = 6,
In = 7,
NotIn = 8
}
A questo punto l'unica cosa che era rimasta da fare è crearmi la mia classe Finder di cui qua riporto un abstract:
public class CustomerFinder
{
#region Social Field
private SearchableField<string> _companyName = null;
private int? _customerId = null;
private SearchableRange<int> _age = null;
public SearchableRange<int> Age
{
get { return _age; }
set { _age = value; }
}
public SearchableField<string> CompanyName
{
get { return _companyName; }
set { _companyName = value; }
}
public int? CustomerId
{
get { return _customerId; }
set { _customerId = value; }
}
#endregion
#region Geographic Fields
private SearchableField<List<Country>> _localities = null;
public SearchableField<List<Country>> Localities
{
get { return _localities; }
set { _localities = value; }
}
#endregion
#region Economic Fields
private SearchableRange<DateTime?> _purchasedDates;
private SearchableRange<decimal> _amount;
public SearchableRange<DateTime?> PurchasedDates
{
get { return _purchasedDates; }
set { _purchasedDates = value; }
}
public SearchableRange<decimal> Amount
{
get { return _amount; }
set { _amount = value; }
}
#endregion
}
ed il suo utilizzo è stato una cosa del genere:
CustomerFinder cf = new CustomerFinder();
cf.CompanyName = new SearchableField<string>("Gates",FinderOperator.Contains);
cf.Age = new SearchableField<int>(25,FinderOperator.GreaterOrEqualThan);
CustomerPersistence persistence = new CustomerPersistence();
IList<Customer> list = persistence.Find(cf);
Logicamente ci sarebbe ancora tanto da fare.....ma se qualcuno ha tempo potrebbe estendere ancora di più, no?