Reputation: 16353
I am clearly missing something (hopefully obvious), and I have had no luck with Google so far.
I have a Parent-Child relationship mapped as follows
Simplified Parent Map
public sealed class ParentMap : ClassMap<ParentEntity>
{
public ParentMap()
{
Table("Parent");
Component(x => x.Thumbprint);
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.ServeralNotNullableProperties).Not.Nullable();
HasMany(x => x.Children).KeyColumn("ChildId")
.Inverse()
.LazyLoad()
.Cascade
.AllDeleteOrphan();
References(x => x.SomeUnrelatedLookupColumn).Column("LookupColumnId").Not.Nullable();
}
}
Simplified Child Map
public sealed class ChildMap : ClassMap<ChildEntity>
{
public ChildMap()
{
Table("Child");
Component(x => x.Thumbprint);
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.MoreNotNullableProperties).Not.Nullable();
References(x => x.Parent).Column("ParentId").Not.Nullable();
}
}
Simplified Service Method Steps
I then have a service method that retrieves Parent and adds a new Child via some domain method. The underlying NHibernate code boils down to:
1) Session Opened on WCF AfterReceiveRequest (IDispatchMessageInspector)
_sessionFactory.OpenSession();
2) Retrieve existing instance of parent via .Query
_session.Query<ParentEntity>()
.Where(item => item.Id == parentId)
.Fetch(item => item.SomeLookupColumn)
.First();
3) Add new 'Child' entity to 'Parent' via domain object method such as...
public ChildEntity CreateChild()
{
var child = new ChildEntity{ Parent = this };
Children.Add(child);
return child;
}
4) Ultimately calls SaveOrUpdate on 'Parent' entity.
// ChildEntity Id is 0
_session.SaveOrUpdate(parentEntity)
// Expect ChildEntity Id to NOT be 0
Note: Use of SaveOrUpdate Does persist changes to database; use of Merge results in TransientObjectException mentioned below.
5) Finally, transaction committed on WCF BeforeSendReply (IDispatchMessageInspector)
_session.Commit();
The Problem
Typically when an entity is saved, after the call to SaveOrUpdate, the Id of said entity refects the Identity generated by the INSERT statement. However, when I add a new child entity to an existing parent, the associated entity in Children
does not get assigned an Id. I need to pass this Id back to the caller so subsequent calls result in UPDATES and not additional INSERTS.
Why is NHibernate not updating the underlying entity? Do I explicitly have to call SaveOrUpdate on the child (that sucks if that is the case)?
Any thoughts on the matter greatly appreciated. Thanks!
EDIT I should note, that the new child entity IS being saved as expected; I just don't get back the Id assigned.
UPDATE Tried switching to .Merge(~) as suggested by Michael Buen below; resulting in a TransientObjectException telling me to explicitly save my modified child entity before calling merge. I also experimented with .Persist(~) to no avail. Any additional insight greatly appreciated.
object is an unsaved transient instance - save the transient instance before merging: My.NameSpace.ChildEntity
Upvotes: 10
Views: 13917
Reputation: 39393
For detached scenario, use Merge(formerly known as SaveOrUpdateCopy)
Example: http://www.ienablemuch.com/2011/01/nhibernate-saves-your-whole-object.html
Regarding:
Do I explicitly have to call SaveOrUpdate on the child (that sucks if that is the case)?
You don't need to, in fact that's where an ORM like NHibernate shines, it can save the whole object graph, and it really solves the impedance mismatch between the Object and database.
The physical implementation(for example, int or guid) of how a parent and child records(and sub-child records and so on) are linked is completely invisible from the app. You can concentrate well on your objects rather than the under-the-hood plumbing on what datatypes(int,guid,etc) your object relationships should use
[UPDATE]
You can still save the parent and the saving will cascade to children, and can get the child's id too. If you need to get the child id, get it after commit.
You cannot get the child id this way:
s.SaveOrUpdate(parentEntity);
Console.WriteLine("{0}", parentEntity.ChildIdentity.First().ChildId);
tx.Commit();
Must do this:
s.SaveOrUpdate(parentEntity);
tx.Commit();
Console.WriteLine("{0}", parentEntity.ChildIdentity.First().ChildId);
Upvotes: 5
Reputation: 16353
I finally found the time to dig around in the NHibernate code... the reason this is happening makes sense... short answer - the Cascade actions are processed on Session.Commit(); well after the above service operation has completed. Thus, my child objects haven't been refreshed to reflect the assigned ids by the time the service method returns the updated child entity.
Turns out I must call SaveOrUpdate directly on child entity if I need to know the Id immediately.
Upvotes: 13
Reputation: 64628
You could call session.Flush()
, which performs the inserts on the database. This is not ideal because of the impact to performance.
Upvotes: 1
Reputation: 3412
Maybe this will help (It's about Inverse attribute): http://www.emadashi.com/index.php/2008/08/nhibernate-inverse-attribute/
Upvotes: 1