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.  [ 8 posts ] 
Author Message
 Post subject: Conversations with detached objects: reattaching
PostPosted: Wed Jun 13, 2007 4:20 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
We use the "Conversation with detached objects" pattern in long-lived service singletons that cache rarely-updated, low-level entities for performance. The cached entities have several levels of lazy-loaded collection properties, so our service needs to reattach them every time it uses them (so that unpredictable lazy loading will work). We cannot eagerly fetch or otherwise force all of the cached entities' collection properties (and their collection properties, etc) to get loaded when the service starts, because that causes unacceptable startup time (>30 sec) and some superiors say it "wastes memory" (we're talking about anywhere from a thousand to tens of thousands of objects). Most of the objects are rarely used, but the ones that do get accessed are heavily used. It is not possible to know at compile time which ones get heavy use and which don't.

Reattaching the entities seems like a major pain. The problem is that in a small but significant number of cases, another copy of the cached entity to be reattached to a new session -- different instance, same ID -- has already been fetched by the new session by something else. To attach the cached entitiy to the new session, we call session.Lock(entity, LockMode.None) (although wouldn't session.Update(entity) be better?), but if it throws NonUniqueObjectException, then we have to call session.SaveOrUpdateCopy(entity) and set our copy of the cached entity to the different instance returned. It's this last step, that the code doing the attaching has to reset all its references to the different instance returned, that's the hassle.

Does anyone else have this problem? I can't think of an inexpensive way to avoid it. Trying to evict the session's other copy and then call Update() on the copy you're originally trying to attach would just cause the same problems for whatever other code might have references to the other instance you just evicted. Apparently the problem would never occur if the service with the cached objects to reattach always attached them to sessions that it created and only it uses, but that would require it to eat up an additional database connection, and I don't think we can afford that ...


Top
 Profile  
 
 Post subject: Re: Conversations with detached objects: reattaching
PostPosted: Thu Jun 14, 2007 7:59 am 
Senior
Senior

Joined: Thu Feb 09, 2006 1:30 pm
Posts: 172
I totally see your issue here. It looks like a bunch of competing priorities.

Just curious though, why doesn't NHibernate's second level cache work for you if you always need to reattach these objects to a session? Is it that you know the types of objects you need cached, but it's not all instances of those types? If it worked it seems like it could avoid some of your other headaches.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jun 14, 2007 11:15 am 
Regular
Regular

Joined: Thu Nov 23, 2006 10:29 am
Posts: 106
Location: Belgium
Hello,

I'm using NHibernate in a Winforms application in a detached scenario.
And, of course, also had a lot of problems due to lazy initialisation and reattaching objects.

What I did in the end is, whenever I need an object to initialize it's collections, I use entity = session.Get<entitytype>(entity.id) instead of Lock(). Then I initialize it's collections. Example:

Code:
using (ISession session = datastore.getSession())
{
    entity = session.Get<Entity>(entity.id);
   
    NHibernateUtil.Initialize(entity.SomeCollection);
}


When I want to update an entity, I execute some code like here underneath:

Code:
using (ISession session = datastore.getSession())
{
    entity = session.Get<Entity>(entity.id);
   
    entity.ModifySomething = "something";

    session.SaveOrUpdate(entity);
    session.Flush();
}


It works for me...

This approach has also the advantage that NHibernate will start by searching in the second level cache first (because I'm using a Get based on the entities' id) so if the entity is cached, I won't even have a DB roundtrip.
Sometimes, I use session.Refresh(entity) too, but then I know I'll have a DB roundtrip.

_________________
Please rate this post if it helped.

X.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jun 14, 2007 4:12 pm 
Newbie

Joined: Wed Aug 17, 2005 9:39 am
Posts: 5
Hi have two questions about this :

xasp wrote:

Code:
using (ISession session = datastore.getSession())
{
    entity = session.Get<Entity>(entity.id);
   
    NHibernateUtil.Initialize(entity.SomeCollection);
}




Why do you use Initialize method instead a fetch="join" on this association ?

xasp wrote:
Code:
using (ISession session = datastore.getSession())
{
    entity = session.Get<Entity>(entity.id);
   
    entity.ModifySomething = "something";

    session.SaveOrUpdate(entity);
    session.Flush();
}



Why do you use the SaveOrUpdate method in this case ?
Simply use a flush and the refresh goes automatic.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jun 14, 2007 4:31 pm 
Senior
Senior

Joined: Thu Feb 09, 2006 1:30 pm
Posts: 172
wowo wrote:
Hi have two questions about this :

xasp wrote:

Code:
using (ISession session = datastore.getSession())
{
    entity = session.Get<Entity>(entity.id);
   
    NHibernateUtil.Initialize(entity.SomeCollection);
}




Why do you use Initialize method instead a fetch="join" on this association ?



I assume it is because he doesn't want that always to be eagerly fetched, just in this particular case when it is being used.

wowo wrote:
xasp wrote:
Code:
using (ISession session = datastore.getSession())
{
    entity = session.Get<Entity>(entity.id);
   
    entity.ModifySomething = "something";

    session.SaveOrUpdate(entity);
    session.Flush();
}



Why do you use the SaveOrUpdate method in this case ?
Simply use a flush and the refresh goes automatic.


You are correct, SaveOrUpdate is not necessary here. But at the same time it will not break anything, so maybe that's why he added it?


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jun 15, 2007 2:57 am 
Regular
Regular

Joined: Thu Nov 23, 2006 10:29 am
Posts: 106
Location: Belgium
Hello,

(I didn't thought my response would attract that much attention ;-) )

The reason I sometimes use NHibernate.Initialize is because I don't want the overhead of the creation of an ICriteria. When I know for certain I 'll need the data when fetching initially the entity, I'll use an ICriteria with the fetchmode set to join.
But sometimes the object is passed along several methods and events and I'm not sure if
a) the object is bound to a Session or not
b) what collections are initialized or not
In those cases I'll use a Get<> and NHibernate.Initialize (unless there's more than 1 collection to fetch, then I might fall back on an ICriteria).

Anyway, the point in the answer was really just to demonstrate the use of Get<> versus Lock().

SaveOrUpdate() versus Flush() : that's possible, again the code was just an illustration. In my real application I have a Session-object that is a wrapper around an ISession. When I call SaveOrUpdate on my wrapper, it starts a transaction (if there wasn't one already active) and commits it afterwards. I never use Flush().

And even if I would, I think I'd still do it like I pointed out: it seems to me to be much more readable when using session.SaveOrUpdate() explicitely.

_________________
Please rate this post if it helped.

X.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jun 15, 2007 8:08 am 
Senior
Senior

Joined: Thu Feb 09, 2006 1:30 pm
Posts: 172
xasp wrote:
And even if I would, I think I'd still do it like I pointed out: it seems to me to be much more readable when using session.SaveOrUpdate() explicitely.


This point I will debate. It actually is a tricky point, but I think it is points like this that cause so much confusion with NHibernate. The problem is that so many people do not know what SaveOrUpdate() does. I don't doubt that you understand how it works, but the average beginner has no clue. I've had to explain it countless times to most new developers we introduce NHibernate to.

Basically people got used to thinking about persistence and saving to databases. I'm trying to convince people that when you use NHibernate it is not actually the point, but rather a synchronization tool. SaveOrUpdate does not mean run a Save or Update SQL statement right now. This is what most of our new developers think. The reason why? Because that is how most other frameworks/applications worked for them. Put some data somewhere and call the Save method, or call the Update() method. They may think NHibernate is cooler because they can just call SaveOrUpdate(). They have no clue what is going on under the covers, and then make many bad assumptions.

A lot of people would tell me that the object won't be updated without calling SaveOrUpdate().

So all I'm saying is when we say it is clearer what is going on when we show SaveOrUpdate it seems to help confuse people who are new to NHibernate. If it wasn't there, maybe they would wonder why it isn't and ask questions, but otherwise people are likely to just keep thinking you need it there and that is what actually performs the updates.

The Flush() concept still confuses a lot of people too. Why have flush() when I already said to save?


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jun 15, 2007 6:24 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
Regarding the 2nd level cache -- we tried it, but it didn't seem to help, because nearly all of our data retrieval is by HQL and not by loading individual entities by ID.

Anyway, our main issue is the conflict where you want to reattach a detached object, and a different instance with the same ID is already in the session. We can't seem to completely avoid that, and to handle it in the general case through our own Attach() front-end method, it has to take the entity to attach by reference (since if there's already one in the session with the same ID, the caller is forced to switch all their references to that other instance). Then to pass it by reference, it has to be of type object, which means the code calling Attach() has to put it into a variable of type object first. So, reattaching just one object ends up being a mess like this:
Code:
object entity = typedVarWithRealEntity;
EntitySession.Attach(ref entity);
if (!object.ReferenceEquals(entity, typedVarWithRealEntity)
{
    typedVarWithRealEntity = entity;
    // reset additional references to entity ...
}
// Now after all that crap we can finally touch a property
// without fearing "LazyInitializationException: No session"

As you can see, in methods where I have to drill 5 levels deep through entity/collection properties, when every freakin' entity that I need to touch has to be reattached in this way, 80% of my code then becomes this clunky plumbing to reattach. What should be 10 or 20 lines of clear code becomes at least 5 times that amount of confusing code ...

The only thing I can think of to avoid it is if we write our own cache provider which raises an event that a cached object is requested, and all services that need to keep detached objects have to listen to the event and give the cache provider its detached copy of the object if it has it. Then fall back on one of the standard cache providers if it wasn't found in our own "cache" of detached objects ...


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 8 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.