Questa è una di quelle cose che non si drovrebbero fare… ma se avete un db che non potete toccare non è che ci siano molte scappatoie :-)
Il bello è che il tool risponde alla grande, anche se in questo caso deve essere un po’ addomesticato. Lo scenario è questo:
image
Mappato su un db con, più o meno, questa struttura:
image
Nel dominio avete il concetto di Preventivo (Estimate) e di Contratto (Agreement) e le 2 entità sono relazionate tra loro: ad un preventivo potrebbe corrispondere uno ed un solo contratto e ad un contratto potrebbe corrispondere uno ed un solo preventivo.
Il “potrebbe” in entrambe le casistiche è derivante dal fatto che un Preventivo:
  • “in corso” non ha un ancora un contratto;
  • “defunto” non avrà mai un contratto;
In maniera similare un Contratto potrebbe nascere senza essere passato da un Preventivo. La problematica in se non è nulla di trascendentale e qui trovate un ottimo riepilogo del problema e della soluzione, o meglio di parte della soluzione.
I problemi sorgono quando avete la necessità di cercare tutti i preventivi che non hanno un contratto… che dal punto di vista del modello ad oggetti è una cosa del tipo:
IEnumerable<Estimate> list;
list.Where( e => e.Agreement == null );
Ma volete che il tutto sia fatto da NHibernate direttamente in fase di fetch con una bella left-outer-join andando a cercare i preventivi che hanno un contratto con pk a null nel resultset.
Il Mapping
Già la fase di mapping, con il fido Fluent NHibernate, non è proprio delle più intuitive, per come è strutturato il db il mapping da Contratto verso Preventivo è abbastanza triviale:
public AgreementMapping()
{
    this.SchemaIs( "..." );
    this.WithTable( "..." );

    this.References( e => e.Estimate, "EstimateUID" )
        .Cascade.SaveUpdate()
        .Nullable();
Ci limitiamo a definire una “reference” tra il tipo Agreement e il tipo Estimate attraverso al proprietà Agreement.Estimate e specifichiamo il nome della colonna per la FK; spostandoci invece sul tipo Estimate le cose sono un po’ diverse perchè non potete più usare Reference, o perlomeno io non ci sono riuscito:
public EstimateMapping()
{
    this.SchemaIs( "..." );
    this.WithTable( "..." );

    this.HasOne( e => e.Agreement )
        .WithForeignKey()
        .PropertyRef( a => a.Estimate )
        .Cascade.SaveUpdate();
La combinazione vincente è data da HasOne insieme a PropertyRef:
  • HasOne definisce una simil-relazione 1-1 tra Estimate.Agreement e Agreement;
  • la magia la fa PropertyRef perchè istruisce il motore di mapping facendo si che tutte le informazioni per il mapping vengano prese dal mapping della classe Agreement e facendo di conseguensa si che NHibernate sia in grado di generare correttamente gli statement sql;
La cosa va che è un piacere finchè non cerchiamo di fare questo:
var criteria = provider.CreateCriteria<Estimate>();
criteria.Add( Restrictions.IsNull( "Areement" ) );
var data = criteria.List<Estimate>();
Che ci rimbalza perchè siamo dei disgraziati :-D
image
Anche un ipotetico tentativo di questo tipo falisce miseramente:
  criteria.Add( Restrictions.IsNull( "Areement.Key" ) );
Quella cosa non può funzionare, in realtà il giochetto è abbastanza tricky:
var criteria = provider.CreateCriteria<Estimate>();
criteria.CreateCriteria( "Agreement", "a", JoinType.LeftOuterJoin )
    .Add( Restrictions.IsNull( "a.Key" ) );
var data = criteria.List<Estimate>();
è cioè sufficiente creare un alias per la proprietà Agreement e poi andare ad indagare la PK alla ricerca di valori null restituiti dalla left outer join, producendo di fatto questa query:
image
che è esattamente quello che ci serve!
Concludendo
  1. il tool è impressionante, flessibile e potente;
  2. l’uso di Query Specification, come ho già avuto modo di dettagliare, permette di incapsulare tutta la logica degli ICriteria nascondendo e centralizzando i dettagli dell’implementazione;
.m