L'interfaccia del repository generico ha un punto che non è soddisfacente: la specifica delle query. Se ad esempio si volesse cercare un customer che abita a "Sao Paulo" e il cui nome contenga una lettera "a" si può invocare il repository in questo modo.
IList<Customer> AllCustomers = repo.GetByQuery(
delegate(IQueryBuilder qb) {
qb.Equal("AddressInfo.City", "Sao paulo")
.Like("CompanyName", "%a%");
});
In primo luogo la specifica del nome delle proprietà con delle stringhe è error prone come detto precedentemente, ma in generale il problema maggiore è che partizionando il proprio dominio in aggregati non è architetturalmente corretto che il chiamante possa specificare al repository criteri di ricerca che possono essere basati su oggetti aggregati, come ad esempio l'oggetto Address. Come ultimo punto talvolta capita che alcune regole di business siano particolarmente complesse, ad esempio potrei definire un "gold customer" come un cliente che abbia fatto almeno 30 ordini nell'ultimo anno con un importo totale superiore ai 10.000€, in questo caso è consigliabile che l'oggetto root (in questo caso il cliente) definisca direttamente un criterio (Ad esempio GoldCustomer) che può essere utilizzato all'esterno senza sapere effettivamente come è costruito il criterio al suo interno. Una soluzione possibile è creare un oggetto query in questo modo.
8 public class ChainQuery {
9
10 private List<Proc<IQueryBuilder>> _chainOfQueries = new List<Proc<IQueryBuilder>>();
11
12 public ChainQuery Add(Proc<IQueryBuilder> query) {
13 _chainOfQueries.Add(query);
14 return this;
15 }
16
17 /// <summary>
18 /// Build the chain of query
19 /// </summary>
20 /// <returns></returns>
21 private Proc<IQueryBuilder> BuildChainQuery() {
22 return delegate(IQueryBuilder qb) {
23 foreach (Proc<IQueryBuilder> query in _chainOfQueries) {
24 query(qb);
25 }
26 };
27 }
28
29 public static implicit operator Proc<IQueryBuilder>(ChainQuery cq) {
30 return cq.BuildChainQuery();
31 }
32 }
Questo oggetto tiene al suo interno una lista di Proc<IQueryBuilder>, ovvero il nostro Query Object ed è in grado di creare un queryobject componendo in AND tutti i criteri inseriti al suo interno. Un operator Implicito di conversione (linea 29) permette inoltre di convertire automaticamente da questo tipo di oggetto ad un Proc<IQueryBuilder>. Armati di questa classe base procediamo a definire una classe statica nel nostro dominio che contiene un riferimento ad una serie di oggetti query, uno per ogni radice di aggregato.
public static class Query {
public static CustomerQueries Customer {
get {return new CustomerQueries();}
}
}
In questo caso ho solamente l'aggregato Customer, l'oggetto CustomerQueries contiene al suo interno la definizione di tutti i criteri più utilizzati per questo aggregato.
public class CustomerQueries : ChainQuery {
public CustomerQueries City(String cityName) {
Add(delegate(IQueryBuilder qb) {
qb.Equal("AddressInfo.City", cityName);
});
return this;
}
public CustomerQueries Name(String name) {
Add(delegate(IQueryBuilder qb) {
qb.Like("CompanyName", "%" + name + "%");
});
return this;
}
}
Come si può vedere ogni definizione di criterio torna un riferimento all'oggetto query stesso, in questo modo posso comporre le query in maniera decisamente facile. Ereditando dalla classe ChainQuery abbiamo supporto alla composizione di criteri e la possibilità di usufruire dell'operatore di conversione implicito che ci permette di invocare il repository in questo modo.
IList<Customer> AllCustomers = _repo.GetByQuery(
Query.Customer.City("Sao Paulo").Name("a"));
In questo caso il codice è sicuramente più leggibile, non è necessario specificare le proprietà con stringhe e cosa più importante si passa per un oggetto che racchiude tutti i criteri più comuni da utilizzare con l'aggregato desiderato. Il funzionamento è semplice, l'oggetto statico Query crea nella proprietà Customer un nuovo CustomerQueries, a questo punto io posso chiamare tutti i suoi criteri specificando i valori, ogni chiamata torna un riferimento allo stesso oggetto, in questo modo io posso concatenare le chiamate (fowler fluent interface) e fermarmi quando necessario dato che un oggetto CustomerQueries può essere convertito automaticamente in un Proc<IQueryBuilder> e quindi passato al metodo GetByQuery() del repository.
Alk.