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