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

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

Reply via email to