2009-11-30 31 views
0

J'ai une classe d'envoi qui regroupe une classe FreightDateTime. En même temps, la classe FreightDateTime est également agrégée par la classe GoodsItem. De la même manière, FreightDateTime est associé à un certain nombre d'autres classes que j'ai omises pour l'instant.NHibernate: la relation plusieurs-à-plusieurs ne parvient pas à enregistrer les objets enfants en premier (soit: "ne peut pas insérer de null" ou: "objet transitoire")

Pour éviter une base de données FreightDateTime avec une clé étrangère ConsignmentId, une clé étrangère GoodsItemId, etc., j'ai décidé que l'association devrait être plusieurs-à-plusieurs. De cette façon, NHibernate génère une table d'association pour chaque relation (ConsigmentFreightDateTimes, GoodsItemFreightDateTimes), ce qui est plus logique. Ainsi, dans le fichier de mappage, l'association recherche, par exemple, dans le fichier de mappage. comme ceci:

<bag name="DateTimes" table="FreightDateTimes" lazy="false" cascade="all"> 
    <key column="ConsignmentId"/> 
    <many-to-many class="Logistics.FreightDateTime, Logistics" column="DateTimeId" /> 
</bag> 

Réglage cascade "tous les" rendements:

System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'DateTimeId', table 'LogiGate.dbo.FreightDateTimes'; column does not allow nulls. INSERT fails. 

Réglage cascade à "none" rendements:

NHibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: Logistics.FreightDateTime 

Dans les deux cas, cela signifie que NHibernate tente pour enregistrer l'instance Consignment, bien que les instances FreightDateTime enfant n'aient pas été enregistrées. Dans le premier cas, la clé étrangère est toujours 'nulle', qui ne peut donc pas être insérée dans la table résultante, et dans le second cas, NHibernate est conscient que l'instance n'a pas encore été sauvegardée, et lève donc l'exception. Donc, la question est de savoir comment je peux obtenir NHibernate pour enregistrer toutes les instances enfants en premier, sans lui dire explicitement de le faire. J'ai le pressentiment qu'autoriser des valeurs nulles sur la colonne DateTimeId ferait l'affaire, mais je pense que ce n'est ni souhaitable ni possible.

Répondre

1

Essayez de mapper l'association de l'autre côté, mais utilisez l'attribut inverse = "true" de ce côté. Donc, créez dans le fichier de mappage FreightDateTime un sac pour mapper l'association autant que plusieurs avec la consignation.

En outre, j'ai répondu à une question similaire ici: What is the correct way to define many-to-many relationships in NHibernate to allow deletes but avoiding duplicate records

La lecture de la question et ma réponse sera peut-être vous aider à comprendre ce qui se passe avec votre many-to-many et peut-être vous donner un indice pour la Solution. La recommandation finale est une sorte de dernier recours.

La réponse dans la question ci-dessus est juste pour donner une idée de ce qui se passe à travers le problème différent d'une autre personne.

Une solution consisterait à mapper explicitement la table d'association. Si vos tables sont: personne, note et la table d'association (tableau X) est PersonNote Vos applications doivent ressembler à ça:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
        assembly="..." 
        namespace="..."> 

    <class name="Person" table="Person" lazy="true"> 

    <id name="PersonId"> 
     <generator class="native" /> 
    </id> 
    <property name="FirstName" /> 
    ..... 
    <bag name="PersonNotes" generic="true" inverse="true" lazy="true" cascade="none"> 
     <key column="PersonId"/> 
     <one-to-many class="PersonNote"/> 
    </bag> 

    <bag name="Notes" table="PersonNote" cascade="save-update"> 
     <key column="PersonId"></key> 
     <many-to-many class="Note" column="NoteId"></many-to-many> 
    </bag> 

    </class> 

</hibernate-mapping> 

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
        assembly="..." 
        namespace="..."> 

    <class name="Note" table="Note" lazy="true"> 

    <id name="NoteId" unsaved-value="0"> 
     <generator class="native" /> 
    </id> 
    <property name="Title" /> 
    .... 
    <bag name="PersonNotes" inverse="true" lazy="true" cascade="all-delete-orphan"> 
     <key column="NoteId"/> 
     <one-to-many class="PersonNote"/> 
    </bag> 

    <bag name="People" table="PersonNote" inverse="true" cascade="save-update" generic="true"> 
     <key column="NoteId"></key> 
     <many-to-many class="Person" column="PersonId"></many-to-many> 
    </bag> 

    </class> 

</hibernate-mapping> 

Comme il est au-dessus vous permet:

  1. Supprimer une personne et supprimer uniquement l'entrée dans la table d'association sans supprimer l'une des notes
  2. Supprimer une note et supprimer uniquement l'entrée dans la table d'association sans supprimer l'une des entités Person
  3. Enregistrez avec Cascades uniquement du côté Personne en remplissant la collection Person.Notes et en enregistrant la Personne.
  4. Depuis l'inverse = vrai est nécessaire dans le Note.People il n'y a pas un moyen de faire des cascades sauver de ce côté. En remplissant la collection Note.People, puis en enregistrant l'objet Note, vous obtiendrez une insertion dans la table Note et une insertion dans la table Person mais pas d'insertion dans la table d'association. Je suppose que c'est ainsi que fonctionne NHibernate et que je n'ai pas encore trouvé de solution.
  5. Vous pouvez uniquement cascader les entrées de sauvegarde dans la table d'association de manière explicite en ajoutant de nouveaux éléments dans la collection PersonNotes de l'entité Note.

Tous les éléments ci-dessus ont été testés avec des tests unitaires. Vous devrez créer le fichier et la classe de mappage de classe PersonNote pour que cela fonctionne. Si vous avez besoin d'une autre classe pour avoir des notes, disons Organisation, vous n'affectez qu'une autre table d'association à votre schéma appelé OrgnanisationNote et vous faites la même chose que précédemment pour le fichier de mappage d'organisation et le fichier de mappage de notes.

Je dois dire encore une fois que cela devrait être la dernière option pour quiconque veut avoir un contrôle complet sur ses associations plusieurs-à-plusieurs.

+0

L'ajout d'un sac inverse au mappage FreightDateTime demandera à NHibernate d'avoir également une propriété IList correspondante dans la classe FreighDateTime. Mon but est d'éviter de référencer FreightDateTime à Consignment au niveau de la classe et de la table, car FreightDateTime ne devrait pas avoir besoin de "savoir" quoi que ce soit sur les objets qui le possèdent. Comment cela pourrait-il être mis en œuvre? –

+0

Je crois qu'il n'y a pas moyen de contourner cela. Vous devez avoir créé le sac inverse pour que votre mappage fonctionne comme vous le souhaitez. Ce que NHibernate vous permet est de mapper le sac à un membre privé au lieu d'une propriété publique (attribut access = "field") qui ne serait pas accessible depuis l'extérieur de l'implémentation de l'entité. Si vous persistez à ne pas cartographier l'inverse, essayez simplement de voir si cela fonctionne. Ensuite, vous pouvez le faire fonctionner jusqu'à ce que vous trouviez la solution «parfaite» pour vous (s'il y en a une). – tolism7

+0

Merci. Je pense que je préférerais avoir beaucoup de relations un-à-plusieurs, ce qui se traduira probablement par le fait que la table FreightDateTimes se réfère à beaucoup d'autres tables qui ne sont pas liées entre elles, mais que ce soit pour l'instant. J'ai également regardé composite-element et many-to-any, mais ceux-ci sont destinés à différentes situations. Ou que penses-tu? –