2008-12-02 7 views
19

J'ai une hiérarchie à trois niveaux d'entités: Customer-Order-Line, que je voudrais récupérer en entier pour un client donné, en utilisant ISession.Get (id). Je les fragments XML suivants:NHibernate Eager Récupération sur plusieurs niveaux

customer.hbm.xml:

<bag name="Orders" cascade="all-delete-orphan" inverse="false" fetch="join"> 
    <key column="CustomerID" /> 
    <one-to-many class="Order" /> 
</bag> 

order.hbm.xml:

<bag name="Lines" cascade="all-delete-orphan" inverse="false" fetch="join"> 
    <key column="OrderID" /> 
    <one-to-many class="Line" /> 
</bag> 

Je l'ai utilisé fetch = "join" attribut pour indiquer que Je veux récupérer les entités enfants pour chaque parent, et cela a construit le bon SQL:

SELECT 
    customer0_.ID AS ID8_2_, 
    customer0_.Name AS Name8_2_, 
    orders1_.CustomerID AS CustomerID__4_, 
    orders1_.ID AS ID4_, 
    orders1_.ID AS ID9_0_, 
    orders1_.PostalAddress AS PostalAd2_9_0_, 
    orders1_.OrderDate AS OrderDate9_0_, 
    lines2_.OrderID AS OrderID__5_, 
    lines2_.ID AS ID5_, 
    lines2_.ID AS ID10_1_, 
    lines2_.[LineNo] AS column2_10_1_, 
    lines2_.Quantity AS Quantity10_1_, 
    lines2_.ProductID AS ProductID10_1_ 

FROM Customer customer0_ 

LEFT JOIN [Order] orders1_ 
     ON customer0_.ID=orders1_.CustomerID 

LEFT JOIN Line lines2_ 
     ON orders1_.ID=lines2_.OrderID 

WHERE customer0_.ID=1 

Jusqu'à présent, cela ressemble bon - SQL retourne l'ensemble correct d'enregistrements (avec un seul orderid distinct), mais quand j'exécute un test pour confirmer le bon nombre d'entités (de NH) pour les commandes et les lignes, j'obtiens des résultats erronés

I I devrait obtenir (à partir de mes données de test), 1xOrder et 4xLine, cependant, je reçois 4xOrder et 4xLine. Il semble que NH ne reconnaisse pas le groupe «répétitif» des informations de commande dans le jeu de résultats, ni ne «réutilise» correctement l'entité de commande. J'utilise tous les nombres entiers (PKs), et j'ai essayé d'implémenter IComparable de T et IEquatable de T en utilisant cet ID, dans l'espoir que NH verra l'égalité de ces entités. J'ai également essayé de surenchérir Equals et GetHashCode pour utiliser l'ID. Aucune de ces «tentatives» n'a réussi.

Est-ce que «multiple leveled fetch» ​​est une opération prise en charge pour NH, et si oui, y a-t-il un paramètre XML requis (ou un autre mécanisme) pour le prendre en charge? NB: J'ai utilisé la solution de Sirocco avec quelques modifications à mon propre code pour finalement résoudre celle-ci. le xml doit être changé de bag à set, pour toutes les collections, et les droits eux-mêmes ont été changés pour implémenter IComparable <>, qui est une exigence d'un ensemble pour l'unicité à établir.

Notez l'utilisation d'un champ ID interne. Ceci est requis pour les nouvelles entités (transitoires), sinon ils n'auront pas d'ID initialement (mon modèle les a fournis lors de leur sauvegarde).

+0

[Cette réponse] (http://stackoverflow.com/questions/5266180/fighting-cartesian-product-x-join-when-using-nhibernate-3-0-0/5285739#5285739) m'a aidé à voir comment utiliser les requêtes QueryOver et Future pour récupérer les enfants et les petits-enfants sans retourner les doublons. La technique consiste à décomposer la tâche en différentes requêtes SQL qui sont exécutées dans un seul aller-retour à la base de données. –

Répondre

21

Vous obtenez 4XOrder et 4XLines car la jointure avec les lignes double les résultats.Vous pouvez définir un Transformer sur l'ICriteria comme:

.SetResultTransformer(new DistinctRootEntityResultTransformer()) 
+3

J'ai découvert que cela résout effectivement le problème, * si * les mappages sont changés de bag à set, et j'implémente le IComparable nécessaire sur la classe de base. –

+1

Intéressant, j'ai utilisé le DistinctRoot .... mais toujours avec un Bag, et jamais implémenté IComparable. Mais n'a jamais fait pour le chargement de niveau 3 :) – sirrocco

+0

Aussi, je pense qu'il serait préférable de ne pas charger le client, puis aller Customer.Orders. En général, vous ne demandez pas à un client de vous communiquer ses commandes. Il serait donc préférable d'avoir un Repo et: GetOrdersForCustomerId (int id). – sirrocco

5

Je viens de lire Ayende's Blogpost où il a utilisé l'exemple suivant:

session.CreateCriteria(typeof(Post)) 
    .SetFetchMode("Comments", FetchMode.Eager) 
    .List(); 

Dans une requête de critères pour éviter de charger Lazy sur une requête particulière

Peut-être que cela peut vous aider.

+0

Vous venez de me sauver quelques tests de régression d'heures à cause de ce commentaire. Infiniment reconnaissant. –

+1

Cela ne fonctionnera pas "sur plusieurs niveaux" comme l'indique la question. L'utilisation de FetchMode.Eager pour plusieurs niveaux donnera un produit cartésien. Le code SQL correct est généré, mais NHibernate ne le triera pas pour vous. –

0

@Tigraine: votre requête ne renvoie que Publier avec Commentaires. Cela amène tous les messages avec tous les commentaires (2 niveaux). Ce que Ben demande au client de commander à LineItem (niveau 3). @Ben: à ma connaissance nHibernate ne supporte pas le chargement hâtif jusqu'à 3 niveau pour le moment. Hibernate le soutient toi.

+0

@Sheraz - J'espère que vous avez tort :-) MAIS, si vous avez raison, pourquoi aurait-il générer le bon SQL? La chance? –

0

J'avais le même problème. Voir ceci thread. Je n'ai pas eu de solution mais un indice de Fabio. Utilisez Set au lieu du sac. Et ça a marché.

Donc, ma suggestion est d'essayer d'utiliser ensemble. Vous ne devez pas utiliser la collecte Iesi utilisation IDictonary et NH est heureux

public override IEnumerable<Baseline> GetAll() 
{ 
    var baselines = Session.CreateQuery(@" from Baseline b 
              left join fetch b.BaselineMilestones bm 
              left join fetch bm.BaselineMilestonePrevious ") 
              .SetResultTransformer(Transformers.DistinctRootEntity) 
              .List<Baseline>(); 
    return baselines; 
} 
+2

Passer de à a fonctionné pour moi aussi, mais une trace SQL montre qu'un produit cartésien est encore produit sur le serveur. Cela signifie que NHibernate triera et filtrera les résultats à sa fin, pour remplir correctement les collections enfant et grand-enfant. Cela ne sera probablement pas idéal dans de nombreuses situations, car cela peut signifier que des milliers ou des millions d'enregistrements sont traînés à travers le fil, puis beaucoup de traitement pour construire le graphique d'objet correct. –

1

Si vous devez garder votre tête-à-manys comme des sacs, vous pouvez émettre 2 requêtes, chacune avec seulement 1 niveau de la hiérarchie . par exemple, quelque chose comme ceci:

var temp = session.CreateCriteria(typeof(Order)) 
    .SetFetchMode("Lines", NHibernate.FetchMode.Eager) 
    .Add(Expression.Eq("Customer.ID", id)) 
    .List(); 

var customer = session.CreateCriteria(typeof(Customer)) 
    .SetFetchMode("Orders", NHibernate.FetchMode.Eager) 
    .Add(Expression.Eq("ID", id)) 
    .UniqueResult(); 

Les lignes sont bien chargées dans le cache NH dans la première requête, donc ils ne seront pas besoin paresseux chargement lors de l'accès plus tard, par exemple customer.Orders [0] .Lines [0].

Questions connexes