Reputation: 74605
A projectparticipant may be a member of many groups and a group may have many projectparticipants
I'm finding that when I create 2 ProjectParticipants (this works) and then populate the Project.Groups
collection with 3 new groups and add the participants to the relevant groups (Group A has participant 1, Group B has participant 2, Group C has participants 1 and 2), I encounter a "StaleStateException - Batch update returned unexpected row count from update; actual row count: 0, expected: 3". I would expect nHibernate to INSERT the new groups but it's running an UPDATE query and balking that they don't exist. It doesn't get as far as assigning the the participants to the groups
Here are the mappings:
//ProjectMap: A Project..
Id(x => x.Id).GeneratedBy.GuidComb().UnsavedValue(Guid.Empty);
HasMany(x => x.Participants)
.Table("ProjectParticipants")
.KeyColumn("ProjectId")
.ApplyFilter(DeletedDateFilter.FilterName)
.Cascade.AllDeleteOrphan()
.Inverse();
HasMany(x => x.Groups)
.Table("ProjectGroups")
.KeyColumn("ProjectId")
.Cascade.AllDeleteOrphan()
.Inverse();
//ProjectParticipantMap: A ProjectParticipant…
Id(x => x.Id).GeneratedBy.GuidComb().UnsavedValue(Guid.Empty);
References(x => x.Project)
.Column("ProjectId")
.LazyLoad(Laziness.Proxy);
HasManyToMany(x => x.Groups)
.Table("ProjectGroupParticipants")
.ParentKeyColumn("ProjectParticipantId")
.ChildKeyColumn("ProjectGroupId");
//GroupMap: A Group...
Id(e => e.Id).GeneratedBy.Assigned().UnsavedValue(Guid.Empty);
References(e => e.Project)
.Column("ProjectId")
.LazyLoad(Laziness.Proxy);
HasManyToMany(x => x.Participants)
.Table("ProjectGroupParticipants")
.ParentKeyColumn("ProjectGroupId")
.ChildKeyColumn("ProjectParticipantId")
.ApplyChildFilter(DeletedDateFilter.FilterName);
The tables are:
[ProjectParticipants] 1-->M [ProjectGroupParticipants] M<--1 [ProjectGroups]
M M
\---------------->1 [Project] 1<--------------------/
Here are the SQLs being run by nHibernate:
--presume this is adding the first participant - I find him in the db
INSERT INTO ProjectParticipants (CreatedDate, ModifiedDate, DeletedDate, FirstName, LastName, Timezone, Email, Pseudonym, Role, ProjectId, UserId, MobileNumber, Id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
--presume this is adding the second participant - I find her in the DB
INSERT INTO ProjectParticipants (CreatedDate, ModifiedDate, DeletedDate, FirstName, LastName, Timezone, Email, Pseudonym, Role, ProjectId, UserId, MobileNumber, Id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
--not sure what this is doing
UPDATE Projects SET CreatedDate = ?, ModifiedDate = ?, LogoUrl = ?, LogoFilename = ?, Client = ?, Name = ?, Description = ?, LastAccessedDate = ? WHERE Id = ?
--not sure what this operation is for, but at this point in time NO GROUP EXISTs for this project ID
SELECT … FROM ProjectGroups groups0_ WHERE groups0_.ProjectId=?
--not sure what this is for either?
UPDATE Projects SET CreatedDate = ?, ModifiedDate = ?, LogoUrl = ?, LogoFilename = ?, Client = ?, Name = ?, Description = ?, LastAccessedDate = ? WHERE Id = ?
-- I've no idea why this is an UPDATE instead of an INSERT, but it will certainly update 0 rows instead of X, because no groups exist
UPDATE ProjectGroups SET CreatedDate = ?, ModifiedDate = ?, DeletedDate = ?, Name = ?, ProjectId = ? WHERE Id = ?
Exception thrown: 'NHibernate.StaleStateException' in NHibernate.dll
Batch update returned unexpected row count from update; actual row count: 0; expected: 3
[ UPDATE ProjectGroups SET CreatedDate = @p0, ModifiedDate = @p1, DeletedDate = @p2, Name = @p3, ProjectId = @p4 WHERE Id = @p5 ]
So why did nHibernate come to think that its local entity had already ben saved and was hence available to UPDATE? The SQL generated should be an insert, but I'm not sure how it manages sync between local cache and DB to know if entities already exist or not
Slightly puzzled, that this used to work in NH 2.x, but since an upgrade to latest (5.x) this exception has started appearing.
Upvotes: 0
Views: 257
Reputation: 2357
Slightly puzzled, that this used to work in NH 2.x,
Handling of unsaved-value
was indeed changed in 5.2 with this pull request.
If I understand correctly this PR fixed some cases where provided unsaved-value
mapping was ignored for assigned identifiers.
So it seems you have incorrect unsaved-value
mapping for your entities with assigned identifier. From given data it's unclear how your expect NHibernate to determine if entity is transient. With your mapping if Id
is not equal to Guid.Empty
NHibernate will trigger UPDATE
statement for all cascaded entities and it seems that's exact behavior you see.
If you want it to check database when entity is not present in session - set it to "undefined"
instead:
Id(x => x.Id).GeneratedBy.GuidComb().UnsavedValue("undefined");
If you want it to always save entity - set it to "any"
.
Read spec with explanations for all other possible values. Also check this similar issue.
Upvotes: 2