-->
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.  [ 13 posts ] 
Author Message
 Post subject: PersistentObjectException: object was an uninitialized proxy
PostPosted: Tue Dec 27, 2005 3:02 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
I have an entity A with a many-to-one relationship to entity B. Both entities are lazy. If I do the following:

1. Create session1
2. Load entity A (and B) using session1
3. Close (and dispose) session 1
4. Modify entity A
5. Create session2
6. Call session2.SaveOrUpdateCopy(A)

Then I get this exception:

Code:
[PersistentObjectException: object was an uninitialized proxy for: B]
   NHibernate.Impl.SessionImpl.Unproxy(Object maybeProxy) +189
   NHibernate.Impl.SessionImpl.DoCopy(Object obj, Object id, IDictionary copiedAlready) +579
   NHibernate.Impl.SessionImpl.Copy(Object obj, IDictionary copiedAlready) +38
   NHibernate.Engine.ActionCopyClass.Cascade(ISessionImplementor session, Object child, Object anything) +65
   NHibernate.Engine.Cascades.Cascade(ISessionImplementor session, Object child, IType type, CascadingAction action, CascadeStyle style, CascadePoint cascadeTo, Object anything) +200
   NHibernate.Engine.Cascades.Cascade(ISessionImplementor session, IClassPersister persister, Object parent, CascadingAction action, CascadePoint cascadeTo, Object anything) +262
   NHibernate.Impl.SessionImpl.DoCopy(Object obj, Object id, IDictionary copiedAlready) +838
   NHibernate.Impl.SessionImpl.SaveOrUpdateCopy(Object obj) +76


I've searched all the forums, and also did a Google search, and found only one similar topic:

http://forums.hibernate.org/viewtopic.php?t=937363&highlight=&sid=4ff838f697db02847419d8fe3168651c

The author of the original topic clearly defines the problem, which appears to be a bug in Hibernate itself, but no solution is posted. The original topic was written a year ago.

Like the author of the original post discovered, it doesn't matter if the instance of B referenced by the instance of A passed to SaveOrUpdateCopy() is an initialized proxy or a real instance of B, I always get the same exception. Examination of B through the debugger clearly shows that it is an initialized proxy.

I can't IM the author of the original post, the Hibernate forums that NHibernate merged with don't support that :( Please advise, this is a showstopper for me :(


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 27, 2005 3:58 pm 
Regular
Regular

Joined: Fri Jun 11, 2004 6:27 am
Posts: 81
Location: Yaroslavl, Russia
To be honest, i think you must get the exception at the step 4, not 6. I mean, if it's really a question of a uninitialized proxy and this proxy is correct (all necessary properites are virtual) then you should get the error when you try to modify it. If not, then it is a bug and it's the question to Sergey (or Hammet maybe).

_________________
Best,
Andrew Mayorov // BYTE-force


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 27, 2005 4:03 pm 
Regular
Regular

Joined: Fri Jun 11, 2004 6:27 am
Posts: 81
Location: Yaroslavl, Russia
I'm sorry to appear again but i want to clarify. If you have uninitialized proxy afer the step 3, and you've got no error at the step 4, then there is perhaps a bug in NH. But regardless of this, you have the bug in logic - you can't expect unitialized proxy to be OK, after its loading session is closed.

_________________
Best,
Andrew Mayorov // BYTE-force


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 27, 2005 4:17 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
The problem is that the instance of B referred to by A that I pass to SaveOrUpdateCopy() isn't an uninitialized proxy. NHiberhate appears to be complaining about another instance of B it creates, internal to the call to SaveOrUpdateCopy().


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 27, 2005 6:23 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
I've tracked down the bug in the code. In Impl\SessionImpl.cs, SessionImpl.SaveOrUpdateCopy() ends up calling SessionImpl.DoCopy(), which in NHibernate 1.0.1 starts out like this (my comments are marked with // ***

Code:
public object DoCopy( object obj, object id, IDictionary copiedAlready )
{
   if( obj == null )
   {
      return null;
   }

   if( obj is INHibernateProxy )
   {
      LazyInitializer li = NHibernateProxyHelper.GetLazyInitializer( ( INHibernateProxy ) obj );
      if( li.IsUninitialized )
      {
         return Load( li.PersistentClass, li.Identifier ); // EARLY EXIT!
      }
      else
      {
         // *** My object is not uninitialized,
         // *** so it ends up here
         obj = li.GetImplementation();
      }
   }

   if( copiedAlready.Contains( obj ) )
   {
      return obj; // EARLY EXIT!
   }

   EntityEntry entry = GetEntry( obj );
   if( entry != null )
   {
      if( id != null && entry.Id.Equals( id ) )
      {
         return obj; //EARLY EXIT!
      }
      // else copy from one persistent instance to another!
   }

   System.Type clazz = obj.GetType();
   IClassPersister persister = GetClassPersister( clazz );

   object result;
   object target;
   if( id == null && persister.IsUnsaved( obj ) )
   {
      copiedAlready[ obj ] = obj;
      SaveWithGeneratedIdentifier( obj, Cascades.CascadingAction.ActionCopy, copiedAlready );
      result = obj; // TODO: Handle its proxy (reassociate it, I suppose)
      target = obj;
   }
   else
   {
      if( id == null )
      {
         id = persister.GetIdentifier( obj );
      }
      // *** My object gets to here ...
      result = Get( clazz, id );
      if( result == null )
      {
         copiedAlready[ obj ] = obj;
         SaveWithGeneratedIdentifier( obj, Cascades.CascadingAction.ActionCopy, copiedAlready );
         result = obj; // TODO: Could it have a proxy?
         target = obj;
      }
      else
      {
         target = Unproxy( result );
...


The code reaches the call to Get(), which calls DoLoadByClass():

Code:
private object DoLoadByClass( System.Type clazz, object id, bool checkDeleted, bool allowProxyCreation )
{
   if( log.IsDebugEnabled )
   {
      log.Debug( "loading " + MessageHelper.InfoString( clazz, id ) );
   }

   IClassPersister persister = GetClassPersister( clazz );
   if( !persister.HasProxy )
   {
      // this class has no proxies (so do a shortcut)
      return DoLoad( clazz, id, null, LockMode.None, checkDeleted );
   }
   else
   {
      Key key = new Key( id, persister );
      object proxy = null;

      if( GetEntity( key ) != null )
      {
         // return existing object or initialized proxy (unless deleted)
         return ProxyFor(
            persister,
            key,
            DoLoad( clazz, id, null, LockMode.None, checkDeleted )
            );
      }
      else if( ( proxy = proxiesByKey[ key ] ) != null )
      {
         // return existing uninitizlied proxy
         // *** The code gets here!!!
         return NarrowProxy( proxy, persister, key, null );


The problem is that the code reaches the section returning a narrow (uninitialized) proxy. Back in DoCopy(), after returning from Get(), the following code is executed:

Code:
      if( result == null )
      {
         copiedAlready[ obj ] = obj;
         SaveWithGeneratedIdentifier( obj, Cascades.CascadingAction.ActionCopy, copiedAlready );
         result = obj; // TODO: Could it have a proxy?
         target = obj;
      }
      else
      {
         // *** The code reaches here,
         // *** since Get() succeeded ...
         target = Unproxy( result );


Finally, in Unproxy(), we have

Code:
private object Unproxy( object maybeProxy )
{
   if( maybeProxy is INHibernateProxy )
   {
      INHibernateProxy proxy = ( INHibernateProxy ) maybeProxy;
      LazyInitializer li = NHibernateProxyHelper.GetLazyInitializer( proxy );
      if( li.IsUninitialized )
      {
         throw new PersistentObjectException( string.Format( "object was an uninitialized proxy for: {0}", li.PersistentClass.Name ) );


In conclusion, it looks like DoCopy() should needs to force initialization on the object returned by Get(), since the subsequent call to Unproxy() requires it to be initialized.

Please fix, this is a showstopper for us :(


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 27, 2005 6:54 pm 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
I have this bookmarked and I will look at it.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 27, 2005 7:30 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
Thanks, Sergey.

Looking into reattaching objects loaded from one session (which no longer exists) into a new session, I see stuff in the documentation about calling ISession.Lock(). The use of ISession.Lock(LockMode.None) appears mysterious. I thought ISession.SaveOrUpdateCopy() took care of reattaching objects to the new session for you ...

Anyway, I tried calling session.Lock() on all the objects referenced by the one I want to save, using LockMode.Read if the object hasn't been modified (by me), or LockMode.None if it's been modified or will be deleted. Then calls to session.SaveOrUpdateCopy() for the modified objects don't throw an exception anymore, but they also don't persist my changes :(

I'll keep experimenting with things to work around this issue. If anyone can clarify why ISession.Lock(myDetachedObject, LockMode.None) would ever need to be called on an object to be saved through SaveOrUpdateCopy (i.e. why SaveOrUpdateCopy() can't do it automatically), please do so ...

p.s. If calling ISession.Lock(myDetachedObject, LockMode.None) is really necessary, it would be much nicer if it were called something more intuitive, like ISession.Reattach(myDetachedObject). However, since the API is already set in stone, the chance of getting it refactored doesn't look good to me ...


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 28, 2005 7:58 am 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
It actually looks to me you don't even need SaveOrUpdateCopy, just plain SaveOrUpdate would do. Here's a condensed explanation of the methods:

Object states:
- transient = does not exist in the database (i.e. not yet saved or already deleted) => is not attached to a session (only objects that exist in the database may be attached to a session)
- persistent = exists in the database, is attached to an open session
- detached = exists in the database, is not attached to an open session

Methods:
- Lock(object, LockMode.None) - reattach a definitely unmodified detached object to a session (detached => persistent)
- Update(object) - reattach a possibly modified detached object to a session (detached => persistent)
- Save(object) - make transient object persistent (transient => persistent)
- SaveOrUpdate(object) - either save or update depending on the object state (any state => persistent)

- SaveOrUpdateCopy(object) - don't change the state of the object itself, instead load a persistent copy in the session and merge the changes onto that (this operation is called merge in Hibernate 3).


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 28, 2005 8:26 am 
Senior
Senior

Joined: Thu Aug 25, 2005 3:35 am
Posts: 160
this is starting to become a very very interesting thread. ;-)

So, I have an object with a collection (cascades to all) loaded on session A, and the detached.

In my client I will add objects to it's collection.

Then on the server I wish to save my object, which I had presumed would save the objects in the collection as well.

I can not use Lock: dirty collection exception which I now understand since the object was modified.

I can not use Update: NHibernate.HibernateException : SQL insert, update or delete failed (expected affected row count: 1, actual affected row count: 0). Possible causes: the row was modified or deleted by another user, or a trigger is reporting misleading row count.
Also understandable now, since the objects in the collection have not yet been added to the session.

Therefor, the correct procedure is to:
Code:
foreach(object in my collection) session.SaveOrUpdate(object)

to attach those transient objects to the session and then:
Code:
session.update(mainObject)

to attach my detached object.

Is this correct?

Shouldn't the call to Update on my mainObject, also try to make persistent my transient object in it's collections?


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 28, 2005 8:39 am 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
Cascades use SaveOrUpdate internally, so your child objects have to have working unsaved-values (and an identifier generator that's different from "assigned").


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 28, 2005 8:44 am 
Senior
Senior

Joined: Thu Aug 25, 2005 3:35 am
Posts: 160
thanks, I can live with that ;-)


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 28, 2005 12:37 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
The reason I'm calling SaveOrUpdateCopy() rather than SaveOrUpdate() is because some time ago I ran into the following scenario:

1. Execute a query that brings back objects a1 and a2 of type A. One of type A's properties, A.B, references type B, and a1 references b1, also returned by this original query.

2. In a 2nd session, load b1' (the same key value as b1), set property a2.B to b1'. The code executing this has no idea that an equivalent object, b1, is already associated with a1 as a1.B.

3. In a 3rd session, reattach a1 and a2, and attempt to flush the session. You will get an exception that multiple instances of the "same" object exist in the 3rd session's cache -- b1 referenced by a1.B, and b1' referenced by a2.B.

Apparently the solution to this scenario is to reattach objects to the 3rd session using SaveOrUpdateCopy() rather than SaveOrUpdate(). Apparently SaveOrUpdateCopy() can be used anywhere SaveOrUpdate() can, and tolerates these duplicate objects (as long as none are modified), but it's slower.

Anyway, I discovered that I had set up my all my mappings to cascade save and update. As I think about it, I don't know why I would really need cascading saves and updates, so I got rid of them. My original problem then went away.

So, the problem still seems to be there if you have cascading saves & updates enabled, but my problem appears to be solved :)


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 28, 2005 1:29 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
I did some additional tests, and got these results:

A. cascade save-update, unsaved-value is not set up properly, call SaveOrUpdateCopy() --> exception

B. cascade save-update, unsaved-value is set up properly, call SaveOrUpdateCopy() --> changes are not persisted

C. cascade none, unsaved-value is not set up properly, call SaveOrUpdateCopy() --> changes are persisted

D. cascade none, unsaved-value is set up properly, call SaveOrUpdateCopy() --> changes are persisted

I got the same results whether I called SaveOrUpdate() or SaveOrUpdateCopy().


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