Our application has the concept of Stories and the concept of Tags. A story can have many tags applied to it, and a tag can be applied to many stories, making the relationship many-to-many. The two tables, Stories and Tags are bridged with a third, StoriesToTags.
The relevant pieces of the mapping files are as follows:
Here's the mapping from Story to Tag:
<class name="Story" table="Stories">
...
<set fetch="subselect" name="Tags" table="StoriesToTags">
<key>
<column name="StoryId" />
</key>
<many-to-many class="Tag">
<column name="TagId" />
</many-to-many>
</set>
</class>
And the inverse relationship from Tag to Story:
<class name="Tag" table="Tags">
...
<set fetch="subselect" inverse="true" name="Stories" table="StoriesToTags">
<key>
<column name="TagId" />
</key>
<many-to-many class="Story">
<column name="StoryId" />
</many-to-many>
</set>
</class>
As you can see, we're using the subselect fetch strategy to avoid the N+1 query problem. Everything works great, until you attempt to page a result using LINQ:
IQueryable<Story> stories = GetStories(...).TakePage(pageNumber, pageSize);
After running this query, NHibernate deletes the relationships (records in StoriesToTags) for all stories that were not loaded in the query. It only seems to occur when the tags are specifically loaded (that is, the subselect is triggered). The relationships are not deleted if we switch to a join or select fetch strategy, but that causes N+1 queries to be executed.
My best guess is that NHibernate thinks the tags have been orphaned, but we haven't set any cascades on the collections. Also, as far as I can tell, setting a cascade has no effect.
This process worked great under NHibernate 2.x and NHibernate.Linq. We didn't see the issue with deletion occur until we moved to NHibernate 3.x, which has LINQ support built-in. I'm not sure it makes a difference, but for what it's worth, we're using SQL Server with identity keys.
Any thoughts? I initially thought I was doing something insanely stupid, but I've tried basically every permutation of mapping and we can't seem to eliminate the issue.
Edit: Another interesting piece of information. If you call session.IsDirty()
before closing the session, the issue doesn't occur. I suspect that this is because collection changes aren't persisted between flushes, but I can't decipher NHibernate's source well enough to know for certain.
ICollection<T>
. – Mollymollycoddle