Session still dirty after refresh

8 views
Skip to first unread message

Maximilian Csuk

unread,
Feb 9, 2010, 6:57:26 PM2/9/10
to nhusers
Hi there,

This is a continuation of this thread:
http://groups.google.com/group/nhusers/browse_thread/thread/4d94ed0ba3bb0886?hl=en&pli=1

I created a unit-test which shows what NHibernate is (IMHO) doing
wrong:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using ExclamationMark.Models.Tests.Models;
using ExclamationMark.Models.Tests.Mocks.Models.Domain;
using NHibernate;
using NHibernate.Cfg;
using ExclamationMark.Models.Interceptors.Session;
using ExclamationMark.Models.Factories;
using NHibernate.Tool.hbm2ddl;

namespace ExclamationMark.Models.Tests.NHBugs
{
public class RefreshDirtySessionBug
{
[SetUp]
public virtual void SetUp()
{
var cfg = new Configuration();
cfg.Configure();
cfg.AddAssembly("ExclamationMark.Models.Tests");
sessionFactory = cfg.BuildSessionFactory();

// export schema
new SchemaExport(cfg).Execute(false, true, false);
}

[Test]
public void Test()
{
// save test-object graph
TestObject o = new TestObject();
o.TestValue = "TestValue";
AssociatedTestObject ao1 = new AssociatedTestObject();
ao1.AssociatedTestValue = "AssociatedTestValue1";
ao1.TestObject = o;
AssociatedTestObject ao2 = new AssociatedTestObject();
ao2.AssociatedTestValue = "AssociatedTestValue2";
ao2.TestObject = o;
using (ISession s = sessionFactory.OpenSession())
using (ITransaction t = s.BeginTransaction())
{
s.Save(o);

t.Commit();
s.Flush();

Assert.IsFalse(s.IsDirty());
}

// get object from DB again, change values (and
associations), refresh it and check dirtiness
using (ISession s = sessionFactory.OpenSession())
using (ITransaction t = s.BeginTransaction())
{
o = s.Get<TestObject>(o.TestObjectID);
Assert.AreEqual("TestValue", o.TestValue);

o.TestValue = "ChangedTestValue";
Assert.IsTrue(s.IsDirty());
Assert.AreEqual("ChangedTestValue", o.TestValue);

ao1 =
s.Get<AssociatedTestObject>(ao1.AssoiatedTestObjectID);
ao1.AssociatedTestValue =
"ChangedAssociatedTestValue1";
Assert.IsTrue(s.IsDirty());
Assert.AreEqual("ChangedAssociatedTestValue1",
ao1.AssociatedTestValue);

ao2 =
s.Get<AssociatedTestObject>(ao2.AssoiatedTestObjectID);
ao2.TestObject = null;
s.Delete(ao2);
Assert.IsTrue(s.IsDirty());
Assert.AreEqual(1, o.AssociatedTestObjects.Count);

s.Refresh(o);

// asserts which still work
Assert.AreEqual("TestValue", o.TestValue);
Assert.AreEqual(2, o.AssociatedTestObjects.Count);
Assert.AreEqual("AssociatedTestValue1",
ao1.AssociatedTestValue);

// assert which doesn't work
Assert.IsFalse(s.IsDirty());
}
}

protected ISessionFactory sessionFactory;
}
}


The mappings look like this:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="ExclamationMark.Models.Tests"
namespace="ExclamationMark.Models.Tests.Mocks.Models.Domain">
<class name="TestObject">

<id name="TestObjectID" column="testObjectID">
<generator class="guid" />
</id>

<property name="TestValue" column="testValue" not-null="true" />

<set name="AssociatedTestObjects" cascade="all" inverse="true">
<key column="parentID" />
<one-to-many class="AssociatedTestObject" />
</set>

</class>
</hibernate-mapping>


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="ExclamationMark.Models.Tests"
namespace="ExclamationMark.Models.Tests.Mocks.Models.Domain">
<class name="AssociatedTestObject">

<id name="AssoiatedTestObjectID" column="associatedTestObjectID">
<generator class="guid" />
</id>

<property name="AssociatedTestValue" column="testValue" not-
null="true" />

<many-to-one name="TestObject" column="parentID" class="TestObject"
not-null="true" access="field.lowercase" />

</class>
</hibernate-mapping>


The classes are also quite straightforward:


using System;
using ExclamationMark.Models.Domain;
using Iesi.Collections.Generic;

namespace ExclamationMark.Models.Tests.Mocks.Models.Domain
{
public class TestObject
{
public TestObject()
{
AssociatedTestObjects = new
HashedSet<AssociatedTestObject>();
}

public virtual Guid TestObjectID { get; set; }
public virtual string TestValue { get; set; }
public virtual ISet<AssociatedTestObject>
AssociatedTestObjects { get; private set; }
}
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExclamationMark.Models.Domain;

namespace ExclamationMark.Models.Tests.Mocks.Models.Domain
{
public class AssociatedTestObject
{
public virtual Guid AssoiatedTestObjectID { get; set; }
public virtual string AssociatedTestValue { get; set; }

public virtual TestObject TestObject
{
get { return testobject; }
set
{
if (Equals(testobject, value))
return; // no change needed

if (testobject != null)
testobject.AssociatedTestObjects.Remove(this); //
remove from old object

testobject = value;

if (testobject != null)
testobject.AssociatedTestObjects.Add(this); // add
to new object
}
}

private TestObject testobject;
}
}


Have a look at the end of the test-case. A refresh of the (only)
changed entity doesn't set the session back to a non-dirty state. Why
is this?

The workaround posted by Fabio made the problem even worse. With:
s.Evict(o);
o = s.Get<TestObject>(o.TestObjectID);
instead of
s.Refresh(o);
this line already fails:
Assert.AreEqual("AssociatedTestValue1", ao1.AssociatedTestValue);
which kind of makes sense.

Is this enough to file a bug report? The session should be clean
afterwards, shouldn't it? And what would you propose as a work-around
(after/instead of Refresh())?

Regards,
Maximilian Csuk

Diego Mijelshon

unread,
Feb 10, 2010, 6:31:29 AM2/10/10
to nhu...@googlegroups.com
Have you checked your mappings for possible ghosts?


What happens if you Flush the session?

   Diego



--
You received this message because you are subscribed to the Google Groups "nhusers" group.
To post to this group, send email to nhu...@googlegroups.com.
To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nhusers?hl=en.


Maximilian Csuk

unread,
Feb 10, 2010, 11:58:42 AM2/10/10
to nhusers
Hi,

I have run the GhostBuster. Nothing special came up. The test passes
and it says "No instances" for every entity, which is right, I think.

If I flush the session right after calling Refresh(), it gives me this
exception:

ExclamationMark.Models.Tests.NHBugs.RefreshDirtySessionBug.Test:
NHibernate.ObjectDeletedException : deleted object would be re-saved
by cascade (remove deleted object from associations)
[ExclamationMark.Models.Tests.Mocks.Models.Domain.AssociatedTestObject#77c831d1-2adf-40f5-9cb8-
feebbedec119]

I would interpret it this way: after calling Delete() on the
associated object, NHibernate marks it as deleted. However, the
Refresh() doesn't remove this mark. So NHibernate still thinks it
should be deleted afterwards. But the Object-entity holds a reference
to this "deleted" entity. When Flush()ing, NHibernate doesn't know
what to do.
I am not sure how NHibernate "marks" entities to delete but IMHO a
Refresh() should also reset this.
Is my guess right?

On 10 Feb., 12:31, Diego Mijelshon <di...@mijelshon.com.ar> wrote:
> Have you checked your mappings for possible ghosts?
>

> Please readhttp://jfromaniello.blogspot.com/2010/02/nhibernate-ghostbuster-versi...
>
> <http://jfromaniello.blogspot.com/2010/02/nhibernate-ghostbuster-versi...>What


> happens if you Flush the session?
>
>    Diego
>

> On Tue, Feb 9, 2010 at 20:57, Maximilian Csuk <maximilian.c...@gmx.at>wrote:
>
> > Hi there,
>
> > This is a continuation of this thread:
>

> >http://groups.google.com/group/nhusers/browse_thread/thread/4d94ed0ba...

> > nhusers+u...@googlegroups.com<nhusers%2Bunsu...@googlegroups.com>

Reply all
Reply to author
Forward
0 new messages