2
votes

I'm having trouble with some NHibernate Code.Basically I'm trying to keep session lifetime as short as possible to minimize state information in the application.

I find it rather hard to describe my problem without being specific, so I'll just go ahead and use Article and Category metaphors.

public class Article
{
    public virtual long Id { get; set; }
    public virtual string Name { get; set; }
    public virtual Category Category { get; set; }
}
public class Category
{
    public virtual long Id { get; set; }
    public virtual string Name { get; set; }
    public override string ToString() { return "cat-" + this.Id.ToString(); }
}
public class ArticleMapping : ClassMap<Article>
{
    public ArticleMapping()
    {
        this.Id( x => x.Id, "id" ).GeneratedBy.Assigned();
        this.Map( x => x.Name, "name" );
        this.References( x => x.Category ).Fetch.Join().Cascade.None();
    }
}
public class CategoryMapping : ClassMap<Category>
{
    public CategoryMapping()
    {
        this.ReadOnly();
        this.Id(x => x.Id, "id").GeneratedBy.Assigned();
        this.Map(x => x.Name, "name");
    }
}

What I do is create a new Article, give it a name, Assign a Category from a list loaded via the method described below and attempt to save.

I get the following warning:

Unable to determine if cat-1 with assigned identifier is transient or detached

If you take a look at the model and mapping code, you will see that cat-1 should not be persisted at all - it is a Category and defined readonly and non-cascading.

//code that loads the list of categories
IStatelessSession session = this.SessionService.GetStatelessSession();
IList<Category> cats = session.CreateCriteria<Category>().List<Category>();
this.SessionService.EndSession(session);

//code that's called to save the instance
ISession session = this.SessionService.GetSession();
using (ITransaction transaction = session.BeginTransaction())
{
  session.SaveOrUpdate(article);
  transaction.Commit();
}
this.SessionService.EndSession(session);

If I call the Save method again, it all goes banana-shaped:

Exception:

NHibernate.StaleStateException: Unexpected row count: 0; expected: 1

Stacktrace:

at NHibernate.AdoNet.Expectations.BasicExpectation.VerifyOutcomeNonBatched(Int32 rowCount, IDbCommand statement) at NHibernate.AdoNet.NonBatchingBatcher.AddToBatch(IExpectation expectation)

at NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session)

at NHibernate.Persister.Entity.AbstractEntityPersister.UpdateOrInsert(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session)

at NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Int32[] dirtyFieldA first chance exception of type 'NHibernate.StaleStateException' occurred in NHibernate.dll s, Boolean hasDirtyCollection, Object[] oldFields, Object oldVersion, Object obj, Object rowId, ISessionImplementor session)

at NHibernate.Action.EntityUpdateAction.Execute()

at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)

at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)

at NHibernate.Engine.ActionQueue.ExecuteActions()

at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)

I used to think NHibernate was free from black magic, but this makes me wanna visit my voodoo priest to ask for help from the other realm...

Does anyone have any idea where the StaleStateException is coming from? Where did I go wrong this time around?

Thanks in advance

Sebi

2

2 Answers

2
votes

not sure, but the problem might be the fact that you load the object using one session, and update it using another.
in that scenario, the object is, indeed- detached.
try using the Bind() function to bind the object to the second session, or consider using just one session (note that session != db connection. meaning- having a session open should not be too expensive, and is not a bad idea).

1
votes

Yuck! Sometimes it helps to check all local stuff before shouting it out to into the world...There was a Database trigger somewhere that overwrote the ID NHibernate had fetched from the sequence.

Meaning :

  • The warning was fully comprehensible, as the category assigned to the article had no session state, so that hasto be determined via fetch or alternatively session.Refresh(), which has the same result.

  • The StaleObjectStateException had to occur, since the row, being persistent, could not be located in the table (thanks, Mr. Trigger!)

Sorry for bothering y'all...