Alkampfer's Place

Il blog di Gian Maria Ricci
posts - 659, comments - 871, trackbacks - 80

My Links

News

Gian Maria Ricci Mvp Logo CCSVI in Multiple Sclerosis

English Blog

Tag Cloud

Article Categories

Archives

Post Categories

Image Galleries

I miei siti

Siti utili

Considerazioni sulla fluent interface

Supponiamo di dovere permettere dal DAL una ricerca su un business object che ha i campi: Nome, Cognome, Settore (intero tipizzato), Qualifica (Intero Tipizzato), dataNascita e dataAssunzione . La ricerca sui campi stringa è di tipo like, quella sui campi tipizzati è di eguaglianza, mentre per le date posso scegliere se filtrare per date minori o maggiori di un certo valore. Si vuole creare una unica funzione nel DAL capace di effettuare una ricerca su una qualsiasi combinazione di questi parametri.

La soluzione più grezza è quella di generare la query SQL manualmente componendo i vari pezzi di clausola where, ma purtroppo questo approccio è error prone e non è sicuramente efficiente. Una soluzione efficace è sfruttare la "short circuit evaluation" di SQL server, e fare ad esempio una stored che accetta tutti questi parametri. Ogni parametro ha un valore di default che indica alla stored di non usare quel preciso parametro per la ricerca, in questo modo si può avere una unica stored che effettua una ricerca per un numero variabile di parametri.

ALTER PROCEDURE TestSearch 
    @nome nvarchar(50) = NULL,
    @cognome nvarchar(50) = NULL,
    @settore int = NULL,
    @qualifica int = NULL,
    @nascitaLess datetime = NULL,
    @nascitaGreater datetime = NULL,
    @assunzioneLess datetime = NULL,
    @assunzioneGreater datetime = NULL
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Insert statements for procedure here
    SELECT * 
    FROM dbo.QueryObjectTest
    WHERE
    (@nome is NULL OR nome like @nome + '%') and
    (@cognome is NULL or cognome like @cognome + '%')
    …etc etc

Il segreto sta nel fatto che quando la prima condizione è verificata (Es @nome is null) la seconda parte non viene nemmeno verificata evitando di sprecare tempo. Naturalmente questa stored verrà richiamata dal DataAccessLayer con una funzione Search sull'oggetto EmployeeDao, funzione che accetta tutti i parametri della stored, usando Int32? al posto del normale Int32 in modo da poter passare null come valore. Il problema di questa tecnica è che la funzione risultante è poco "parlante", ad esempio si potrebbe creare questa funzione.

public IList<Employee> Search(
   
String nome,
   
String cognome,
   
Int32? settore,
   
Int32? qualifica,
   
DateTime? nascitaLess,
   
DateTime? nascitaGreater,
   
DateTime? assunzioneLess,
   
DateTime? assunzioneGreater) {

Ma poi l'invocazione sarebbe quantomeno scomoda, che ricerca effettua la funzione sottostante?

empdao.Search(nullnull, 1, nullnullnew DateTime(1974, 1, 1), nullnull);

In questo caso è necessario andare a considerare la posizione dei parametri per capire che il primo 1 si riferisce al settore, e la data si riferisce al parametro nascitaGreater, per cui si sta cercando un impiegato del settore 1 nato dopo il 1974 e quindi con meno di 33 anni. Come si può facilmente capire l'uso della funzione search è quantomeno complesso, poco intuitivo, c'è poco ausilio dall'intellisense ed è facile sbagliarsi passando un parametro in un posto sbagliato. La soluzione è adottare una fluent interface aggiungendo una semplice classe al progetto

internal class EmployeeSearchParameters {
   
public String nome;
   
public String cognome;
   
public Int32? settore;
   
public Int32? qualifica;
   
public DateTime? nascitaLess;
   
public DateTime? nascitaGreater;
   
public DateTime? assunzioneLess;
   
public DateTime? assunzioneGreater;
 
   
public static EmployeeSearchParameters Create() {
      
return new EmployeeSearchParameters();
   }
 
   
public EmployeeSearchParameters ByName(String name) {
      nome = name;
      
return this;
   }
 
   
public EmployeeSearchParameters BySurname(String surname) {
      cognome = surname;
      
return this;
   }
 
   
public EmployeeSearchParameters BySector(Int32 sector) {
      settore = sector;
      
return this;
   }
 
   
public EmployeeSearchParameters ByBirthDateGreater(DateTime date) {
      nascitaGreater = date;
      
return this;
   }
… ETC ETC 
}

Come si può vedere questa classe contiene tutti i parametri della nostra search(), ma ha una interfaccia esplicita per impostarli, naturalmente è necessario aggiungere al Dao una funzione che accetta questo parametro.

public IList<Employee> Search(EmployeeSearchParameters search) {
   
return Search(search.nome, search.cognome, search.settore,
         search.qualifica, search.nascitaLess, search.nascitaGreater,
         search.assunzioneLess, search.assunzioneGreater);
}

La funzione è banale ed è un semplice wrapper alla funzione search() scritta in precedenza, ora però il codice client può eseguire la ricerca precedente in questo modo.

empdao.Search(
   
EmployeeSearchParameters.Create()
   .BySector(1)
   .ByBirthDateGreater(
new DateTime(1974, 1, 1)));

Che è sicuramente più semplice, ha pieno supporto dall'intellisense ed è indubbiamente molto, ma molto più leggibile della ricerca precedente. Una soluzione alternativa è dotare la classe EmployeeSearchParameter di un riferimento al Dao e di una funzione di esecuzione che delega la ricerca al Dao.

private EmployeeDao _dao;
 
public EmployeeSearchParameters(EmployeeDao dao) {
   
this._dao = dao;
}
public IList<Employee> Execute() {
   
return _dao.Search(this);
}

Ora si aggiunge una ulteriore funzione nel Dao.

public EmployeeSearchParameters Search() {
   
return new EmployeeSearchParameters(this); 
}

A questo punto la ricerca può essere fatta in maniera ancora più coincisa.

empdao.Search()
   .BySector(1)
   .ByBirthDateGreater(
new DateTime(1974, 1, 1))
   .Execute();

Alk.

 

 

 

Print | posted on mercoledì 5 settembre 2007 10:29 | Filed Under [ .NET ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET