
NHibernate: nullable one-to-one
Il bello è che il tool risponde alla grande, anche se in questo caso deve essere un po’ addomesticato. Lo scenario è questo:

Mappato su un db con, più o meno, questa struttura:

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;
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:
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.IEnumerable<Estimate> list; list.Where( e => e.Agreement == null );
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:
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 AgreementMapping() { this.SchemaIs( "..." ); this.WithTable( "..." ); this.References( e => e.Estimate, "EstimateUID" ) .Cascade.SaveUpdate() .Nullable();
La combinazione vincente è data da HasOne insieme a PropertyRef:public EstimateMapping() { this.SchemaIs( "..." ); this.WithTable( "..." ); this.HasOne( e => e.Agreement ) .WithForeignKey() .PropertyRef( a => a.Estimate ) .Cascade.SaveUpdate();
- 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;
Che ci rimbalza perchè siamo dei disgraziati :-Dvar criteria = provider.CreateCriteria<Estimate>(); criteria.Add( Restrictions.IsNull( "Areement" ) ); var data = criteria.List<Estimate>();

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:è 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:var criteria = provider.CreateCriteria<Estimate>(); criteria.CreateCriteria( "Agreement", "a", JoinType.LeftOuterJoin ) .Add( Restrictions.IsNull( "a.Key" ) ); var data = criteria.List<Estimate>();

che è esattamente quello che ci serve!
Concludendo
- il tool è impressionante, flessibile e potente;
- 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;