StaleStateException when clearing collection

59 views
Skip to first unread message

Daniel Ricardo Castro Alvarado

unread,
Dec 27, 2013, 10:27:21 PM12/27/13
to nhu...@googlegroups.com
(I'm sending my post again because the previous one was not showing after some days)

Hello everyone,

I'm making a test method for an application and I'm having trouble getting it to work correctly.

In my model, one Case has many Specimen (but one Specimen is associated with only one Case).
I'm trying to add one Case with some Specimens to the database and then removing the Specimens (but not deleting the Case), but somehow I get StaleStateException and I can't figure why.

Case.Specimens is mapped as a One to many collection with inverse="false" and cascade="all-delete-orphan". The relationship is bidirectional (i.e. each Specimen has a reference to the Case), though I don't think that's relevant.

        using (var session = DatabaseSetUpFixture.SessionFactory.OpenSession())
            using (var tx = session.BeginTransaction())
            {
                Case original = AddSampleCase(session);
               
                original.Specimens = new List<Specimen>() {
                    CreateSampleSpecimen()
                };

                session.Flush(); // Save the changes
                session.Clear(); // Evict all entities from the session

                Case newVersion = session.Get<Case>(original.Id); // Get the DB version
                newVersion.Specimens.Clear(); // Remove all the specimens

                session.Update(newVersion);
                session.Flush(); // StaleStateException here :S

                // ...
               
                tx.Rollback(); // Leave the test DB untouched
            }
        }

I need to use the same session so I can rollback all the changes made (as this is a test method).
If I comment "newVersion.Specimens.Clear();" the test ends normally (but I need that line, of course), and I don't understand why (even if I change some attributes of the Case)

The stack trace is included below:

en NHibernate.AdoNet.Expectations.BasicExpectation.VerifyOutcomeNonBatched(Int32 rowCount, IDbCommand statement) en p:\nhibernate-core\src\NHibernate\AdoNet\Expectations.cs:línea 29
en NHibernate.AdoNet.NonBatchingBatcher.AddToBatch(IExpectation expectation) en p:\nhibernate-core\src\NHibernate\AdoNet\NonBatchingBatcher.cs:línea 41
en NHibernate.Persister.Entity.AbstractEntityPersister.Delete(Object id, Object version, Int32 j, Object obj, SqlCommandInfo sql, ISessionImplementor session, Object[] loadedState) en p:\nhibernate-core\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:línea 2919
en NHibernate.Persister.Entity.AbstractEntityPersister.Delete(Object id, Object version, Object obj, ISessionImplementor session) en p:\nhibernate-core\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:línea 3102
en NHibernate.Action.EntityDeleteAction.Execute() en p:\nhibernate-core\src\NHibernate\Action\EntityDeleteAction.cs:línea 70
en NHibernate.Engine.ActionQueue.Execute(IExecutable executable) en p:\nhibernate-core\src\NHibernate\Engine\ActionQueue.cs:línea 136
en NHibernate.Engine.ActionQueue.ExecuteActions(IList list) en p:\nhibernate-core\src\NHibernate\Engine\ActionQueue.cs:línea 126
en NHibernate.Engine.ActionQueue.ExecuteActions() en p:\nhibernate-core\src\NHibernate\Engine\ActionQueue.cs:línea 174
en NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session) en p:\nhibernate-core\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs:línea 249
en NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event) en p:\nhibernate-core\src\NHibernate\Event\Default\DefaultFlushEventListener.cs:línea 19
en NHibernate.Impl.SessionImpl.Flush() en p:\nhibernate-core\src\NHibernate\Impl\SessionImpl.cs:línea 1509

So, what is causing StaleStateException?

Thank you very much

Gunnar Liljas

unread,
Dec 28, 2013, 5:07:25 PM12/28/13
to nhu...@googlegroups.com
In general I would suggest inverse=true for bidirectional mappings.


I have been unable to get the same error, though. Could you show both classes and their mappings?

/G






2013/12/28 Daniel Ricardo Castro Alvarado <dan...@gmail.com>

--
You received this message because you are subscribed to the Google Groups "nhusers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nhusers+u...@googlegroups.com.
To post to this group, send email to nhu...@googlegroups.com.
Visit this group at http://groups.google.com/group/nhusers.
For more options, visit https://groups.google.com/groups/opt_out.

Daniel Ricardo Castro Alvarado

unread,
Dec 28, 2013, 10:52:23 PM12/28/13
to nhu...@googlegroups.com
Thanks for your answer.

I wanted to use inverse=true, but if I remind correctly, I was having problems with my optimistic concurrency settings. I want the Case "Version" property is incremented every time I add, update or remove one specimen.

I can show the mappings, but there are a lot of extra properties (I have omitted them). Case is a subclass of RegistryBase, maped with table-per-class strategy (one table for RegistryBase, one for each subclass).

Mapping for RegistryBase:
            Id(x => x.Id)
                .UnsavedValue(Guid.Empty);

            Version(x => x.Version);

            // -- additional properties omitted --
           
            // example of additional association (perhaps relevant)
            References(x => x.Pathologist)
                .Cascade.Merge();

            // One property mapped to a function in the database
            Map(x => x.Priority)
                .Formula("getcasepriority(Id)")
                .ReadOnly()
                .Generated.Always();

Mapping for Case:
            KeyColumn("Id");
           
            HasMany(x => x.Specimens)
                .Cascade.AllDeleteOrphan()
                .BatchSize(Math.Max(1, Properties.Settings.Default.CollectionBatchSize));

            // extra properties omitted

Mapping for Specimen:
            Id(x => x.Id)
                .UnsavedValue(Guid.Empty);

            References(x => x.Case);
            // -- additional properties omitted --
           
            // No version here, I want the optimistic concurrency is handled with the parent Case

I'm using Fluent NHibernate, by the way. I can post the omitted properties if needed, though they're just strings and perhaps references to other unrelated entities.

Thanks again

Daniel Ricardo Castro Alvarado

unread,
Dec 29, 2013, 12:20:03 PM12/29/13
to nhu...@googlegroups.com
Oh, sorry.
I forgot to add the classes:

EntityBase, just to avoid adding Version and Id in each entity
   
abstract public class EntityBase
{
   
public virtual Guid Id { get; protected set; }
   
public virtual int Version { get; set; }
       
   
// Two entities are considered equal if their Ids are the same
   
public override bool Equals(object obj)
   
{
       
if (obj == null)
           
return false;

       
var other = obj as EntityBase;
       
if (other == null)
           
return false;

       
if (Id.Equals(Guid.Empty))
           
return ReferenceEquals(this, obj);
       
return Id.Equals(other.Id) && Version == other.Version;
   
}

   
public override int GetHashCode()
   
{
       
return Id.GetHashCode();
   
}
}



public abstract class RegistryBase : EntityBase
{

   
// -- additional properties omitted --


   
public virtual short Priority { get; protected set; }
}

public abstract class Case : RegistryBase
{
   
// -- additional properties ommited --

   
public virtual IList<Specimen> Specimens { get; set; }
}



El sábado, 28 de diciembre de 2013 17:07:25 UTC-5, Gunnar Liljas escribió:

joshid...@gmail.com

unread,
Dec 29, 2013, 11:11:40 PM12/29/13
to nhu...@googlegroups.com
Hi,
Can you give more details about CreateSpecimen()?
As inverse=true requires references to be set in specimen.

joshid...@gmail.com

unread,
Dec 29, 2013, 11:12:02 PM12/29/13
to nhu...@googlegroups.com

joshid...@gmail.com

unread,
Dec 29, 2013, 11:12:05 PM12/29/13
to nhu...@googlegroups.com

joshid...@gmail.com

unread,
Dec 29, 2013, 11:14:32 PM12/29/13
to nhu...@googlegroups.com

Daniel Ricardo Castro Alvarado

unread,
Dec 30, 2013, 12:27:37 PM12/30/13
to nhu...@googlegroups.com
Hi,

Keep in mind that I have set inverse=false.
Anyway, CreateSpecimen() looks like this:

private Specimen CreateSpecimen() {
   
return new Specimen() { /* additional properties */, Case = original }
}

Daniel Ricardo Castro Alvarado

unread,
Dec 30, 2013, 11:28:35 PM12/30/13
to nhu...@googlegroups.com
Little mistake: CreateSpecimen() must take a reference to the original Case (the previous code won't compile due to that).

I simplified the original code so it looks like this:

       
using (var session = DatabaseSetUpFixture.SessionFactory.OpenSession())
           
using (var tx = session.BeginTransaction())
           
{
               
Case original = AddSampleCase(session);
               
                original
.Specimens = new List<Specimen>() {

                     
new Specimen () { /* additional properties */, Case = original }

               
};

                session
.Flush(); // Save the changes
                session
.Clear(); // Evict all entities from the session

               
Case newVersion = session.Get<Case>(original.Id); // Get the DB version
                newVersion
.Specimens.Clear(); // Remove all the specimens

                session
.Update(newVersion);
                session
.Flush(); // StaleStateException here :S

               
// ...
               
                tx
.Rollback(); // Leave the test DB untouched
           
}
       
}

Daniel Ricardo Castro Alvarado

unread,
Dec 31, 2013, 1:51:01 AM12/31/13
to nhu...@googlegroups.com
I solved it.

It was not NHibernate's fault. In my PostgreSQL database, I had a row level (before update) trigger on "Specimen" table which was returning NULL, so it was basically skipping the NHibernate second update (to associate the Specimen with the Case). That's why I was getting StaleStateException: The update affected 0 rows, and NHibernate expected it to affect 1 row.

Thanks everyone

joshid...@gmail.com

unread,
Jan 2, 2014, 8:30:10 PM1/2/14
to nhu...@googlegroups.com
Hi,
Good to know that you solved your problem! and even better that you updated forum about it. :)

I have a few questions with EntityBase.Equals() from the code below.

Why do you consider Version for Equals()? NHibernate should be able to take care of that if you have Version property mapped. Am I missing something?

Also As per my understanding, while writing back to DB, NHibernate  calls Equals() to check if an object is already in the Session. Then if it finds the object, it will check the Version property from the mapping and decide whether to Update or Add or No changes required on the DB. Right?

Daniel Ricardo Castro Alvarado

unread,
Jan 3, 2014, 1:18:38 PM1/3/14
to nhu...@googlegroups.com
Hi,

I think it has to do with some internal parts of the project. I don't think it's related to a NHibernate bug or something like that.
It's just that, semantically, my application has to differ a Case with version 1 and a Case with version 2.

Anyway, perhaps it looks strange. I will review that part and if I find it's related with NHibernate, I'll come back and let you know.

Greetings

darshan joshi

unread,
Jan 3, 2014, 7:25:16 PM1/3/14
to nhu...@googlegroups.com

Thanks!

You received this message because you are subscribed to a topic in the Google Groups "nhusers" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nhusers/w9dUy2WHUKU/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nhusers+u...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages