These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 9 posts ] 
Author Message
 Post subject: Change identity of saved object prior to persisting
PostPosted: Wed Jul 18, 2007 1:19 pm 
Beginner
Beginner

Joined: Thu May 31, 2007 1:19 pm
Posts: 23
Hi

My objects reflect those in another system. When I commit a transaction that involves the creation of a new object, I need to create the object in the other system and then assign that external Id to my NHibernate object.

Unfortunately, I cannot create the Object in the other system when Save() is called - I can only create the object when I know the transaction is to be comitted (i.e. I cannot create them on Save() and delete them again if Rollback is called)

I was hoping to be able to manipulate the Id in "FlushDirty" in my interceptor, but this doesn't have any effect.

At the moment, I am thinking I shall have to keep a list of new objects myself, then just prior to a commit, iterate through them, creating the associated external object and assigning the Id, prior to calling Save().

Any thoughts on this would be most welcome
Thanks,
Mark.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 18, 2007 6:26 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
Never change entity IDs. Add a property to track the external ID and update your entity with the external ID once you know it. Then you can look up the entity either by the "local" ID (the entity's primary key) and the external ID (which is just a property, though you will most likely want ot put a unique constraint on that column in the underlying database).


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 19, 2007 5:22 am 
Beginner
Beginner

Joined: Thu May 31, 2007 1:19 pm
Posts: 23
Unfortunately, its simply not as simple as that :)

I don't have control over my schema, and our systems just don't allow for that.

The "prescribed" domain object dictate schema is not an option. In fact, if it was, I'd probably be using DevExpress XPO... the nice thing about NHibernate is it's flexible mapping onto existing databases.

My internal schema is very closely coupled with the external system (which is actually another OLEDB providing client on the same client machine!).

I looking through NHibernate's source, I can see it's very geared not to allow the changing of Ids once the object is registered/persisted. I can quite understand :) I shall have to ensure that by the time I register my objects with NHibernate I am quite sure of my Identity :)

Thanks,
Mark.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 19, 2007 1:45 pm 
Beginner
Beginner

Joined: Wed Nov 29, 2006 10:32 am
Posts: 34
One immediate idea is that you just do the Save() "very late" - namely just before the transaction is to be committed. But this often compromises the design - you cannot do the Save()s just wherever the objects are created (or filled enough so that Save() works - there's a subtle difference here when NOT NULLs and FKs come into play).

The next idea (which we probably will use also) is to write a session adapter, which passes all session calls to its session - with the exception of Save(). Save() just registered the objects in the Adapter; and a Flush() or Commit() - which should also only done through the adapter - does the Save() "late".
This works, but to get it really working nicely you must also think about SaveOrUpdate() and possible AutoFlush() calls in queries - implementing the IInterceptor interface in the adapter should do the trick.
(Switching off autoflushing will make it easier, but then you - well - lose autoflushing).

Anyway, the design needs to be checked and tested somewhat carefully ...

Regards
Harald


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 19, 2007 2:30 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
This brings up questions about turning off auto flushing (i.e. setting the FlushMode to Never). Can someone clarify what the consequences are of not allowing NHibernate to auto-flush at certain times? Off the top of my head, I don't see how it would change anything except possibly performance -- NHibernate knows the status of all the dirty entities in the session cache, so what difference does it make to your own session's queries and Get() calls if the changes have been flushed to the database or not? If turning off flushing can cause undesirable side effects, then why is it even offered as an option?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 19, 2007 2:35 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
Thinking about it a little more, I suspect the consequence is that NHibernate isn't smart enough to apply criteria in queries to dirty entities in the session, and add them to the result set if they are not found by the underlying SQL query. That really sucks. In theory couldn't it be smart enough to do so as long as you don't use any vendor-specific expressions in your criteria? Then NHibernate should be able to understand the criteria conditions and apply them to dirty entities in the session.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 20, 2007 6:06 am 
Beginner
Beginner

Joined: Thu May 31, 2007 1:19 pm
Posts: 23
harald_m_mueller wrote:
One immediate idea is that you just do the Save() "very late" - namely just before the transaction is to be committed. But this often compromises the design - you cannot do the Save()s just wherever the objects are created (or filled enough so that Save() works - there's a subtle difference here when NOT NULLs and FKs come into play).


Yes this was my original plan - to "hide" my new objects from NHibernate until I was certain about their Id's

Quote:
The next idea (which we probably will use also) is to write a session adapter, which passes all session calls to its session - with the exception of Save(). Save() just registered the objects in the Adapter; and a Flush() or Commit() - which should also only done through the adapter - does the Save() "late".


Nice - will look into this

Quote:
Anyway, the design needs to be checked and tested somewhat carefully ...


I fear you're right.

Thankfully, I have been able to get some modifications made to our External data source... it now exposes (via COM) a method that returns a valid Id (basically a sort of GUID)... this then allows me to set the Id myself when I create the object (at the point that I intercept NHibernate's flush).

Phew! Nice to be able to keep things simple.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 20, 2007 9:40 am 
Beginner
Beginner

Joined: Wed Nov 29, 2006 10:32 am
Posts: 34
Nels_P_Olsen wrote:
Thinking about it a little more, I suspect the consequence is that NHibernate isn't smart enough to apply criteria in queries to dirty entities in the session, and add them to the result set if they are not found by the underlying SQL query. That really sucks.


Well ... it's not that easy. Actually, in our project we do exactly what you say using our own expression system (using the adapter/wrapper mentioned before; also because NHibernate does not behave "correctly" w.r.t. ORDER BY and MaxResults when doing implicit ploymorphism - see some other thread); but we must hope a little bit that our = operator does the same as e.g. - in our case - SQL Server (which does case-insensitive = in contrast to other databases). There are other examples with type conversions where it is not that easy to mimick the database's behavior.

The next (and IMO biggest) problem are associations: If you need to search for something which has a join to something which is NOT in the session, you'd have to load that associated object - even though the condition might be false! That might kill your performance. Alternatives are splitting the search so that one part happens on the database, one part in memory (what SQL server has to do when you use remote/dsitributed databases). That is VERY complicated ...

So in the end, it's a "design decision" - there is already one engine which can perfectly well do searches by expressions, namely the database; so why conquer huge problems when there is an established concept of how to do it simpler?

Re switching off autoflushing: If you write your business transaction according to the well-known principle "first gather all information, then do the updates", you'll never have a problem when you switch it off (because there are no changes before the reads). However, this principle contradicts clean modularization: "I call a business method here - I don't care whatever it does inside" - it could do its own "read then write" sequence.

The problem mostly comes with loops where later iterations will want to see whether earlier iterations already did something (one example of a "diamond problem", BTW - my belief is that all problems in software engineering are diamond problems - albeit a little with tongue-in-cheek ;) ). But in such cases, doing an explicit Flush() at the end of the loop (or inner business call) does not hurt and will help.

Thus, my recommendation is to switch off autoflushing in a session with Save() problems and and deal with "funny missing data" on a case-by-case basis - both together will be a rare thing in our experience.

Regards
Harald


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 20, 2007 12:00 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
Thanks for your analysis on auto-flushing and why it exists in the first place. Something like this should go in the NHibernate documentation ...


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 9 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.