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.