NHibernate: child composite-id is not updated when using merge()
Mapping:
<class name="PhoneTypeTest" lazy="false" table="PhoneType">
<cache usage="read-write"/>
<id name ="Id" type="Int32" unsaved-value="0">
<generator class="identity"/>
</id>
<bag name="Resources" table="PhoneTypeR开发者_Python百科esource" lazy="false" cascade="all" inverse="true">
<key column="PhoneTypeId" />
<one-to-many class="PhoneTypeTestResource" not-found="ignore"/>
</bag>
</class>
<class name="PhoneTypeTestResource" lazy="false" table="PhoneTypeResource">
<composite-id class="CCCC.ResourcesCompositeKey, DDDD" name="Id">
<key-property name="OwnerId" column="PhoneTypeId"/>
<key-property name="CultureId"/>
</composite-id>
<property name="Name"/>
</class>
Entities:
public class PhoneTypeTest
{
public PhoneTypeTest()
{
Resources = new List<PhoneTypeTestResource>();
}
public virtual int Id { get; set; }
public virtual IList<PhoneTypeTestResource> Resources { get; set; }
}
public class PhoneTypeTestResource
{
public virtual ResourcesCompositeKey Id { get; set; }
public virtual string Name { get; set; }
}
Unit test:
var ent = new PhoneTypeTest();
ent.Resources.Add(new PhoneTypeTestResource { Id = new ResourcesCompositeKey { CultureId = En, OwnerId = 0 }, Name = "Name" });
Session.Merge(ent);
Session.Flush();
Session.Clear();
SQL generated by nHib:
-- statement #1
INSERT INTO PhoneType
DEFAULT VALUES
select SCOPE_IDENTITY()
-- statement #2
SELECT phonetypet0_.PhoneTypeId as PhoneTyp1_61_0_,
phonetypet0_.CultureId as CultureId61_0_,
phonetypet0_.Name as Name61_0_
FROM PhoneTypeResource phonetypet0_
WHERE phonetypet0_.PhoneTypeId = 0 /* @p0 */
and phonetypet0_.CultureId = 'en' /* @p1 */
-- statement #3
INSERT INTO PhoneTypeResource
(Name,
PhoneTypeId,
CultureId)
VALUES ('Name' /* @p0 */,
0 /* @p1 */,
'en' /* @p2 */)
-- statement #4
ERROR:
Could not synchronize database state with session
SO, as you can see, the problem is that nHib does NOT update child a composite-id after its parent was saved, and the attempt to save children fails. Why?? How can I make nHib to update these ids? Also, if I use SaveOrUpdate() instead Merge(), it works fine!! But I must use merge. Please help!
I ended up manually updating child ids after saving - just another workaround of nHibernate's bug. Do not use composite ids or, even better solution, do not use nHibernate..
I had the exact same problem. I had to run an update on some posts before I would do an insert. Then when I Was about to do my insert,I got the error that the object was already in use.
SaveOrUpdate gave me that error, and ofc Update too. Merge worked BUT it only saved the parent object and not the child objects.
The connection though is that the child object is the one responsible to perform the save (in the mapobject.
HasMany(x => x.Info)
.Cascade.SaveUpdate();
so I guess to be fair, it kinda works as intended.
The annoying part is that if you use SaveOrUpdate on a parent object, it will actually update the childs too as it understands the connection. But if you use Merge, it wont understand the connection at all!
Here's a bit of that code
public AlertEntity InsertCapAlerts(AlertEntity capMessage)
{
using (var transaction = _session.BeginTransaction(IsolationLevel.ReadCommitted))
{
var getCap = GetCapAlert(capMessage.Id);
if (getCap != null)
{
InfoEntity infoToSave = capMessage.Infos.FirstOrDefault();
_session.Merge(infoToSave);
_session.Merge(capMessage);
}
else
_session.Save(capMessage);
transaction.Commit();
return capMessage;
}
}
Basicly, what I did is that when I get my new capMessage that I'm going to save, I first check if it already exists in the db. If it doesnt, then fine, just do a Save(). But if it does exist (here's where SaveOrUpdate always crashed) then I just make a new instance of the child record (we only have one child, therefor FirstOrDefault()).
The first attempt I just saved the Child object to the db by using Merge (because SaveOrUpdate still gives me the error due the object being used in a previous session, and yes I did try session.Clear() and Flush and what not). It worked peachy but then I noticed that my childobject no longer points at its parent. So the childobject is now an orphan crying on the streets of the database. So what we need to do is to point the Parent ID again to the child object.
InfoEntity infoToSave = capMessage.Infos.FirstOrDefault();
infoToSave.AlertEntity_id = capMessage.Id;
_session.Merge(infoToSave);
_session.Merge(capMessage);
Why you ask? Because nHibernate is soooo cute that when it runs Merge, it doesn't give a flying dutch about the childrecords (as it does when you run SaveOrUpdate as I mentioned before). So when you use Merge, you have to update all childposts yourself AND you have to point out the Parent again.
If anyone knows a better solution to this. Please do inform me. Otherwise, I hope that this little code example helped anyone in any way :)
精彩评论