NHibernate does not support many-to-many relationships

I am currently using NHibernate as my data access level, using Fluent NHibernate to create mapping files for me. I have two classes: TripItem and TripItemAttributeValue, which have a many-to-many relationship between them.

The display is as follows:

public class TripItemMap : ClassMap<TripItem2> { public TripItemMap() { WithTable("TripItemsInt"); NotLazyLoaded(); Id(x => x.ID).GeneratedBy.Identity().WithUnsavedValue(0); Map(x => x.CreateDate, "CreatedOn").CanNotBeNull(); Map(x => x.ModifyDate, "LastModified").CanNotBeNull(); /* snip */ HasManyToMany<TripItemAttributeValue>(x => x.Attributes).AsBag() .WithTableName("TripItems_TripItemAttributeValues_Link") .WithParentKeyColumn("TripItemId") .WithChildKeyColumn("TripItemAttributeValueId") .LazyLoad(); } } public class TripItemAttributeValueMap : ClassMap<TripItemAttributeValue> { public TripItemAttributeValueMap() { WithTable("TripItemAttributeValues"); Id(x => x.Id).GeneratedBy.Identity(); Map(x => x.Name).CanNotBeNull(); HasManyToMany<TripItem2>(x => x.TripItems).AsBag() .WithTableName("TripItems_TripItemAttributeValues_Link") .WithParentKeyColumn("TripItemAttributeValueId") .WithChildKeyColumn("TripItemId") .LazyLoad(); } } 

At some point in my application, I retrieve existing attributes from the database, add them to tripItem.Attributes, and then save the tripItem object. In the end, TripItems_TripItemAttributeValues_Link never gets any new entries, as a result of which the relationship is not saved.

If this helps, these are the mapping files created by Fluent NHibernate for these classes:

 <?xml version="1.0" encoding="utf-8"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="true" assembly="ETP.Core" namespace="ETP.Core.Domain"> <class name="TripItem2" table="TripItemsInt" xmlns="urn:nhibernate-mapping-2.2" lazy="false"> <id name="ID" column="ID" type="Int32" unsaved-value="0"> <generator class="identity" /> </id> <property name="CreateDate" column="CreatedOn" type="DateTime" not-null="true"> <column name="CreatedOn" /> </property> <property name="ModifyDate" column="LastModified" type="DateTime" not-null="true"> <column name="LastModified" /> </property> <bag name="Attributes" lazy="true" table="TripItems_TripItemAttributeValues_Link"> <key column="TripItemId" /> <many-to-many column="TripItemAttributeValueId" class="ETP.Core.Domain.TripItemAttributeValue, ETP.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </bag> </class> </hibernate-mapping> 

and

 <?xml version="1.0" encoding="utf-8"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="true" assembly="ETP.Core" namespace="ETP.Core.Domain"> <class name="TripItemAttributeValue" table="TripItemAttributeValues" xmlns="urn:nhibernate-mapping-2.2"> <id name="Id" column="Id" type="Int32"> <generator class="identity" /> </id> <property name="Name" column="Name" length="100" type="String" not-null="true"> <column name="Name" /> </property> <bag name="TripItems" lazy="true" table="TripItems_TripItemAttributeValues_Link"> <key column="TripItemAttributeValueId" /> <many-to-many column="TripItemId" class="ETP.Core.Domain.TripItem2, ETP.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </bag> </class> </hibernate-mapping> 

What am I doing wrong here?

+7
nhibernate fluent-nhibernate
source share
9 answers

@efdee

I had the same problem and spent almost two days on it. I had a lot of relationships and the link table was not updated either. I am new to NHibernate, just trying to learn this, so take everything I say with salt.

Well, it turned out that this is not Fluent NHibernate, nor a mapping, but I do not understand how NHibernate works with many-to-many. In a many-to-many relationship, if the collections for both objects are not populated, NHibernate does not store the data in the link table.

Say I have these entities in a many-to-many relationship:

 partial class Contact { public string ContactName {get; set;} public IList Locations {get; set;} } partial class Location { public string LocationName {get; set;} public string LocationAddress {get;set;} public IList Contacts {get;set;} } 

when I add Contact.Locations to the location, I have to make sure that the contact is also present inside location.Contacts.

so for adding location i have this method inside my Contact class.

 public void AddLocation(Location location) { if (!location.Contacts.Contains(this)) { location.Contacts.Add(this); } Locations.Add(location); } 

This seemed to solve my problem, but as I said, I just collect NHibernate and study it, maybe there is a better way. If anyone has a better solution, send a message.

This is a message that pointed me to checking both collections: http://www.coderanch.com/t/217138/Object-Relational-Mapping/link-table-of-ManyToMany-annotation

+8
source share

Call Session.Flush () or use a transaction.

+7
source share

I'm not sure how you do this with Fluent NHibernate, but you need to set the Cascade option in the bag ( TripItems ). As usual, Ayende got useful information about cascading options .

From quick google, I suggest you try:

 HasManyToMany<TripItem2>(x => x.TripItems).AsBag() .WithTableName("TripItems_TripItemAttributeValues_Link") .WithParentKeyColumn("TripItemAttributeValueId") .WithChildKeyColumn("TripItemId") .LazyLoad() /*-->*/ .Cascade.All(); /*<-- this is the bit that should make it work */ 
+2
source share

I also had the same problem - connection data for many-to-many were not saved. I copied the mapping from another one-to-one relationship (modified it for rel-to-many-rel), but retained the inverse = "true" attribute. When I removed this attribute, the problem was resolved.

+2
source share

David Kemp is right: you want to add a cascade to your bag.

I always manually edited (and manually created) the mapping files, so my natural tendency is to put it there. You can do it as follows:

 <bag name="TripItems" lazy="true" table="TripItems_TripItemAttributeValues_Link" cascade="all"> <key column="TripItemAttributeValueId" /> <many-to-many column="TripItemId" class="ETP.Core.Domain.TripItem2, ETP.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </bag> 

I found that keeping my classes β€œclean” and keeping everything related to nHibernate in the .hbm.xml file helps my cleaner. That way, if there is new ORM software that I want to use, I just replace the mapping files and don't rewrite the classes. We use our unit tests to test classes and provide testing capabilities for xml, although I'm kind of like Fluent NHibernate methods.

+1
source share

I have exactly the same problem, but I'm using NHibernate.JetDriver. I tried using the recommended answer without any success. Does anyone know if NHibernate.JetDriver has a limit on many?

Here are my hbm files if anyone is interested in viewing them for a moment:

 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Ace.Docs.Core.Domain" assembly="Ace.Docs.Core"> <class name="Ace.Docs.Core.Domain.Address, Ace.Docs.Core" table="Addresses" lazy="true"> <id name="Id" column="ID"> <generator class="identity" /> </id> <property name="Address1" column="Address1" /> <property name="Address2" column="Address2" /> <property name="City" column="City" /> <property name="EmailAddress" column="EmailAddress" /> <property name="Phone1" column="Phone1" /> <property name="Phone2" column="Phone2" /> <property name="PostalCode" column="PostalCode" /> <property name="StateOrProvince" column="StateOrProvince" /> <many-to-one name="AddressTypeMember" column="AddressTypeID" class="AddressType" /> <bag name="HasPersonalInfo" table="Link_PersonalInfo_Addresses" lazy="true" cascade="save-update" inverse="true" > <key column="AddressID"></key> <many-to-many column="PersonalInfoID" class="PersonalInfo" /> </bag> </class> 

 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Ace.Docs.Core.Domain" assembly="Ace.Docs.Core"> <class name="Ace.Docs.Core.Domain.PersonalInfo, Ace.Docs.Core" table="PersonalInfo" lazy="true"> <id name="Id" column="ID"> <generator class="identity" /> </id> <property name="Prefix" column="Prefix" /> <property name="FirstName" column="FirstName" /> <property name="MiddleName" column="MiddleName" /> <property name="LastName" column="LastName" /> <property name="SIN" column="SIN" /> <property name="Birthdate" column="Birthdate" /> <property name="Note" column="Notes" /> <bag name="HasAddress" table="Link_PersonalInfo_Addresses" lazy="true" cascade="save-update" inverse="true" > <key column="PersonalInfoID"></key> <many-to-many column="AddressID" class="Address" /> </bag> </class> 

+1
source share

I have it and I hope this helps someone else. The problem is that I had the opposite = true on both bags. If you read the excerpt below, you will notice that one of the packages should have the opposite true value:

Note the use of inverse = "true". Once again, this parameter tells NHibernate to ignore the changes made to the category collection and use the other end of the association - the collection of elements - as the view that should be synchronized with the database.

+1
source share

I am afraid that Cascade.All () is not really a solution to my problem - it was one of the things I tried. The problem is not that the items added to the collection are not saved - they are already in the database when they are added to the collection. It is just that entries in the link table are not created. In addition, I think that Cascade.All () will also remove the children, which is undesirable for my scenario. I tried using Cascade.SaveUpdate (), but as I said, this solves something that is not really my problem :-)

However, to make sure, I will repeat this decision and let you know the result.

Regarding class cleanliness, this is a 100% case with Fluent NHibernate. The class mappings you create are C # code files that come with your entity classes, pretty much like .hbm.xml files.

0
source share

I also struggled with this, and came up with a completely different reason for my problems. In my example, if I had an object without any many-to-many relationship, I could just call saveOrUpdate and everything would be fine. But if I had some many-to-many relationship, I had to make sure my saveOrUpdate call was in BeginTransaction and CommitTransaction. I am very new to Nhibernate getaway, so I apologize if this is obvious. But that was not for me.

0
source share

All Articles