dicamo che avete un dominio come questo:
image
e che questo dominio sta dietro un servizio WCF che espone un set di operazioni tra cui una di questo genere:
/// 
/// Finds all the branches that satisfies the given query.
/// 

/// "culture">The culture.
/// "queryDto">The query dto.
/// 
/// A list of branches that satisfies the given criteria.
/// 
[OperationContract( Name = "FindBranchesByQuery" )]
BranchDto[] FindBranches( String culture, BranchesSearchQueryDto queryDto );
L’inghippo è che il vostro scopo è quello di permettere al client di esprimere una possibile query un po’ come se manipolasse direttamente gli “ICriteria” di NHibernate che però ovviamente non possono, e non avrebbe senso, varcare i confini del servizio.
Quello che potete fare è modellare un dominio per esprimere le query, naturalmente semplificato e pensato per il caso d’uso, fatto ad esempio in questo modo:
image
nulla di trascendentale, triviale direi. Quello che si può notare è che tutti implementano l’interfaccia ICriterionBuilder che è definita così:
interface ICriterionBuilder
{
/// 
/// Builds the NHibernate ICriterion that maps this query criteria.
/// 

/// "criteria">The criteria.
/// The new ICriterion.
IEnumerableICriterion> Build( ICriteria criteria );
}
Il metodo Build prende in pasto i Criteria correnti e ritorna un ICriterion che rappresenta il “Criteria” che ha viaggiato attraverso i confini del servizio.
Perchè Build() ha bisogno di un ICriteria?
per un motivo abbastanza semplice, un elemento della nostra query potrebbe aver bisogno di un “association path” di esprimere cioè una query ad esempio su un elemento di una collection esposta da un’entità, una join insomma… Smile questo è ovviamente un problema:
Voglio cercare tutte le “branch” tra i cui attributi ce ne siano uno che si chiama “Services” e che abbia valore “foo” o valore “bar”
Per il client questa cosa si esprime così:
using( var svc = new SearchServiceClient() )
{
var query = new BranchesSearchQueryDto()
{
AttributesCriteria = new Criteria[]
{
new DisjunctionCriteria()
{
OrCriteria = new Criteria[]
{
new AttributeCriteria()
{
Name="Services",
QueryText="foo"
},
new AttributeCriteria()
{
Name="Services",
QueryText="bar"
}
}
}
}
};

var result = svc.FindBranchesByQuery( "it-IT", query );
}
Quando vi arriva al servizio quello che avete bisogno di fare è trasformare il tutto in un ICriteria la cosa è però complicata dalla necessità di risalire all’eventuale Alias che è stato attribuito all’association path espresso dagli AttributesCriteria.
Si può fare…
In maniera anche decisamente semplice:
public override IEnumerableICriterion> Build( ICriteria criteria )
{
var alias = this.GetAttributesAlias( criteria );

var junction = new Disjunction();
junction.Add( Restrictions.Eq( alias + ".Name", this.Name ) );

if( this.Behavior == CompareBehavior.ExactMatch )
{
junction.Add( Restrictions.Eq( alias + ".Value", this.QueryText ) );
}
else
{
junction.Add( Restrictions.Like( alias + ".Value", this.QueryText.AsSqlServerKeyword(), MatchMode.Exact ) );
}

yield return junction;
}

String GetAttributesAlias( ICriteria criteria )
{
const String alias = "attributes";
const String path = "Attributes";

var attributeCriteria = criteria.GetCriteriaByPath( path );
if( attributeCriteria == null )
{
criteria.CreateAlias( "Attributes", alias );
attributeCriteria = criteria.GetCriteriaByPath( path );
}

return attributeCriteria.Alias;
}
Il giochetto sta semplicemente nel chiedere al criteria corrente un “sub criteria” sulla base di un path e se non esiste crearlo e infine recuperarne l’alias.
Ogni volta che ho a che fare con NHibernate sono sempre più affascinato, prossima tappa i Future() e i FutureValue() per gestire in maniera fighissima la paginazione Smile with tongue out
.m