Combattere prodotto cartesiano (x-join) quando si utilizza NHibernate 3.0.0

Sono cattivo in matematica, ma ho un’idea di cosa sia il prodotto cartesiano .
Ecco la mia situazione (semplificata):

public class Project{ public IList Partners{get;set;} } public class Partner{ public IList Costs{get;set;} public IList
Addresses{get;set;} } public class PartnerCosts{ public Money Total{get;set;} } public class Money{ public decimal Amount{get;set;} public int CurrencyCode{get;set;} } public class Address{ public string Street{get;set;} }

Il mio objective è caricare in modo efficace l’intero progetto.

Il problema ovviamente è:

  • Se provo a caricare i partner e i relativi costi, la query restituisce righe di miliardi di dollari
  • Se carico pigro Partner.Costs, db riceve una richiesta di spam (che è un po ‘più veloce del primo approccio)

Come ho letto, una soluzione comune è usare MultiQueries, ma mi piace solo non capirlo.
Quindi spero di imparare attraverso questo esatto esempio.

Come caricare efficacemente l’intero progetto?

Ps sto usando NHibernate 3.0.0.
Per favore, non postare risposte con approcci api in stile hql o stringa.

Ok, ho scritto un esempio per me stesso che riflette la tua struttura e questo dovrebbe funzionare:

 int projectId = 1; // replace that with the id you want // required for the joins in QueryOver Project pAlias = null; Partner paAlias = null; PartnerCosts pcAlias = null; Address aAlias = null; Money mAlias = null; // Query to load the desired project and nothing else var projects = repo.Session.QueryOver(() => pAlias) .Where(p => p.Id == projectId) .Future(); // Query to load the Partners with the Costs (and the Money) var partners = repo.Session.QueryOver(() => paAlias) .JoinAlias(p => p.Project, () => pAlias) .Left.JoinAlias(() => paAlias.Costs, () => pcAlias) .JoinAlias(() => pcAlias.Money, () => mAlias) .Where(() => pAlias.Id == projectId) .Future(); // Query to load the Partners with the Addresses var partners2 = repo.Session.QueryOver(() => paAlias) .JoinAlias(o => o.Project, () => pAlias) .Left.JoinAlias(() => paAlias.Addresses, () => aAlias) .Where(() => pAlias.Id == projectId) .Future(); // when this is executed, the three queries are executed in one roundtrip var list = projects.ToList(); Project project = list.FirstOrDefault(); 

Le mie classi avevano nomi diversi ma riflettevano la stessa identica struttura. Ho sostituito i nomi e spero che non ci siano errori di battitura.

Spiegazione:

Gli alias sono necessari per i join. Ho definito tre query per caricare il Project desiderato, i Partners con i relativi Costs e i Partners con i relativi Addresses . Usando .Futures() pratica, dico a NHibernate di eseguirli in un roundtrip nel momento in cui effettivamente desidero i risultati, usando projects.ToList() .

Ciò comporterà tre istruzioni SQL che vengono effettivamente eseguite in un roundtrip. Le tre istruzioni restituiranno i seguenti risultati: 1) 1 riga con il progetto 2) x righe con i partner e i relativi costi (e il denaro), dove x è il numero totale di costi per i partner del progetto 3) y righe con il Partner e loro indirizzi, dove y è il numero totale di indirizzi per i partner del progetto

Il tuo db dovrebbe restituire 1 + x + y righe, invece di x * y righe, che sarebbe un prodotto cartesiano. Spero che il tuo DB supporti effettivamente questa funzionalità.

Se stai utilizzando Linq sul tuo NHibernate, puoi semplificare la prevenzione del cartesiano con questo:

 int projectId = 1; var p1 = sess.Query().Where(x => x.ProjectId == projectId); p1.FetchMany(x => x.Partners).ToFuture(); sess.Query() .Where(x => x.Project.ProjectId == projectId) .FetchMany(x => x.Costs) .ThenFetch(x => x.Total) .ToFuture(); sess.Query() .Where(x => x.Project.ProjectId == projectId) .FetchMany(x => x.Addresses) .ToFuture(); Project p = p1.ToFuture().Single(); 

Spiegazione dettagliata qui: http://www.ienablemuch.com/2012/08/solving-nhibernate-thenfetchmany.html

Invece di desideroso di prendere più collezioni e ottenere un prodotto cartesiano brutto:

 Person expectedPerson = session.Query() .FetchMany(p => p.Phones) .ThenFetch(p => p.PhoneType) .FetchMany(p => p.Addresses) .Where(x => x.Id == person.Id) .ToList().First(); 

È necessario raggruppare gli oggetti figli in una chiamata al database:

 // create the first query var query = session.Query() .Where(x => x.Id == person.Id); // batch the collections query .FetchMany(x => x.Addresses) .ToFuture(); query .FetchMany(x => x.Phones) .ThenFetch(p => p.PhoneType) .ToFuture(); // execute the queries in one roundtrip Person expectedPerson = query.ToFuture().ToList().First(); 

Ho appena scritto un post sul blog che spiega come evitare di usare Linq, QueryOver o HQL http://blog.raffaeu.com/archive/2014/07/04/nhibernate-fetch-strategies.aspx

Volevo solo contribuire alla risposta davvero utile di Florian. Ho scoperto a malapena che la chiave di tutto questo è l’alias. Gli alias determinano cosa entra in sql e vengono usati come “identificatori” da NHibernate. Il Queryover minimo per caricare correttamente un grafico a tre livelli è questo:

 Project pAlias = null; Partner paAlias = null; IEnumerable x = session.QueryOver(() => pAlias) .Where(p => p.Id == projectId) .Left.JoinAlias(() => pAlias.Partners, () => paAlias) .Future(); session.QueryOver(() => paAlias).Fetch(partner => partner.Costs). .Where(partner => partner.Project.Id == projectId) .Future(); 

La prima query carica il progetto e i suoi partner secondari. La parte importante è l’alias per Partner. L’alias partner viene utilizzato per denominare la seconda query. La seconda query carica partner e costi. Quando viene eseguito come “Multiquery”, Nhibernate “saprà” che la prima e la seconda query sono collegate da paAlias ​​(o meglio i sql generati avranno alias di colonna che sono “identici”). Quindi la seconda query continuerà il caricamento dei partner che erano già stati avviati nella prima query.