2010-08-20 4 views
1

J'essaie de comprendre quelle est la bonne façon de mapper la relation parent-enfant suivante. J'ai une classe parente qui contient des objets enfants. Cependant, le parent a aussi un pointeur vers une instance de l'un des enfants (PrimaryChild)Collection enfant de mappage NHibernate et instance incorporée de l'enfant

Class Parent 

    Public Property Id As Integer? 

    Public Property PrimaryChild As Child 

    Public Property Children As IList(Of Child) 

End Class 

Public Class Child 

    Public Property Id As Integer? 

    Public MyParent As Parent 

End Class 

L'utilisation est quelque chose comme

Dim ch As New Child 
Dim par as New Parent 

ch.MyParent = par 
par.Children.Add(ch) 

par.PrimaryChild = ch 

Session.SaveOrUpdate(par) 

Cependant, quand je fais ça, le PrimaryChild apparaît comme étant une valeur nulle ou transitoire. J'ai mis cascade = "all" dans la collection Children.

Des idées que je fais mal?

Update 1

Ajouté Mappages

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true"> 
    <class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="Parent" table="Parents"> 
    <id name="Id" type="System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> 
     <column name="ID" /> 
    </id> 

    <set access="nosetter.camelcase-underscore" cascade="all-delete-orphan" inverse="true" name="Children" mutable="true"> 
     <key> 
     <column name="ParentID" /> 
     </key> 
     <one-to-many class="Child" /> 
    </set> 
    <many-to-one cascade="save-update" class="Child" name="PrimaryChild"> 
     <column name="PrimaryChildID" not-null="true" /> 
    </many-to-one> 
    <many-to-one cascade="save-update" class="Child" name="SecondaryChild"> 
     <column name="SecondaryChildID" not-null="true" /> 
    </many-to-one> 
    </class> 
</hibernate-mapping> 

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true"> 
    <class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="Child" table="Child"> 
    <id name="Id" type="System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> 
     <column name="ID" /> 
    </id> 
    <many-to-one class="Parent" name="Parent"> 
     <column name="ParentID" not-null="true" /> 
    </many-to-one> 
    </class> 
</hibernate-mapping> 

Répondre

1

Vos tables sont à la recherche comme ceci:

Table Parent 
(
    ... 
    PrimaryChild_FK NOT NULL 
) 

Table Child 
(
    ... 
    Paren_FK NOT NULL 
) 

Pouvez-vous me dire dans quel ordre les données doivent être insérées? Vous ne pouvez ni insérer Parent ni Child, car les deux ont besoin de l'autre pour définir la clé étrangère.(NHibernate insère l'un d'eux et définit le FK sur null, pour le mettre à jour plus tard, mais la base de données se plaint.)

Supprime la contrainte non nulle de l'ensemble. NHibernate n'est pas assez intelligent pour trouver un ordre d'insertion fonctionnel si vous en supprimez simplement un. (AFAIK, les contraintes non nulles dans les fichiers de mapping ne sont en fait utilisées que pour créer le schéma de la base de données).

Et comme déjà mentionné par mathieu, faites l'ensemble inverse et utilisez la même clé étrangère pour les relations parent-enfant et parent-enfants.

+0

Salut Stefan, merci d'avoir répondu. Parent doit être inséré en premier, puis tous les enfants, puis parent doit être mis à jour pour définir la colonne PrimaryChild_FK à l'identité de l'enregistrement enfant correct. Pour que cela fonctionne, PrimaryChild_FK doit-il être NULL? Dans quel cas, quel devrait être le paramètre Cascade pour PrimaryChild? – James

+0

Supprimez donc la contrainte non nulle de primaryChild_FK. Vous devrez peut-être également supprimer d'autres contraintes non nulles. Comme je l'ai dit: NH n'est pas assez intelligent pour changer l'ordre des insertions en fonction de contraintes non nulles. Je voudrais définir le PrimaryChild à cascade tout. –

0

Qu'est-ce que les correspondances avez-vous à ce jour?

PrimaryChild est un un-à-un, Children est un un-à-plusieurs, mais vous pouvez gérer la relation de nombreuses façons différentes. Où sont vos clés étrangères? Si vous mettez le FK sur l'enfant, alors vous voulez que les deux affectent les affectations un-à-un et un-à-plusieurs avec inverse=true sur les deux.

En aparté, ceci:

ch.MyParent = par 
par.Children.Add(ch) 

est une encapsulation massive OO échouent. Parent ne doit pas exposer un IList, car d'autres objets peuvent le manipuler. La classe parent devrait contrôler toutes les manipulations de lui-même. Faites-en un IEnumerable et utilisez une méthode AddChild qui exécute les deux lignes ci-dessus.

+0

Oui, j'utilise déjà IEnumerable, je voulais juste simplifier pour cette question. Merci de l'avoir signalé de toute façon. Je vais mettre à jour mon post original avec les mappings – James

0

J'ai effectué cette tâche avec des outils tels que la bibliothèque NHibernate LINQ.

public class Parent { 
    public Parent() { 
    Children = new List<Child>(); 
    } 
    public virtual IList<Child> Children { get; set; } 
    public virtual Child PrimaryChild { 
     get { 
     return Children.FirstOrDefault(x => x.IsPrimary); 
    } 
    } 
} 

Si vous avez déjà chargé les enfants, alors PrimaryChild est une opération en mémoire. Si vous demandez PrimaryChild en premier, alors ce sera son propre opération de récupération de base de données. Fonctionne bien, IMO.

Et vous devriez regarder la réponse d'Andrew Bullock. Exposer un IList ouvre la porte à une mauvaise conception du domaine. Vous devriez seulement exposer l'énumération.

+1

@James Jarrett fait un point utile ici, mais ce n'est qu'un côté d'une optimisation possible. Cela dépend de la façon dont vous allez accéder à votre graphique d'objets. Ce serait mieux si vous aviez chargé, sinon vous pourriez créer un problème 'Select N + 1' –

+0

Merci pour la réponse. Je ne pense pas que je puisse utiliser cette méthode car il y a en fait 2 propriétés (PrimaryChild, SecondaryChild) qui peuvent pointer vers la même instance enfant donc je ne peux pas utiliser quelque chose comme IsPrimary – James

0

Si votre relation est bidirectionnelle (références parents IE enfants et références parents- enfants), et si la clé étrangère est enfant, vous devez définir l'attribut

inverse="true" 

dans la déclaration de la collection. Sinon cascade ne fonctionne pas bien:

<?xml version="1.0" encoding="utf-8" ?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> 
    <class name="Parent"> 
    <id name="Id" /> 

    <bag name="Children" cascade="all" inverse="true"> 
     <key column="ID_PARENT" /> 
     <one-to-many class="Child"/> 
    </bag> 
    </class> 

    <class name="Children"> 
    <id name="Id" /> 

    <many-to-one name="Parent" column="ID_PARENT" class="Parent" not-null="true" /> 
    </class> 
</hibernate-mapping> 
Questions connexes