Reputation: 35117
I'm curious if anyone could help me resolve an issue of stale state in nHibernate.
First, the .Net class code:
public class Test
{
public static Test Get(int testId) { return Factory.GetTest(testId); }
public Test() { Related = new List<TestRelate>(); }
public virtual int ID { get; protected set; }
public virtual string Name { get; set; }
public virtual IList<TestRelate> Related { get; set; }
public virtual void Delete() { Factory.Delete(this); }
public virtual void Save() { Factory.Save(this); }
}
public class TestRelate
{
protected TestRelate() { }
public TestRelate(Test test) { TestID = test.ID; }
public virtual int ID { get; protected set; }
public virtual int TestID { get; set; }
public virtual string Data { get; set; }
public virtual void Delete() { Factory.Delete(this); }
public virtual void Save() { Factory.Save(this); }
}
class Factory
{
public static Test GetTest(int testId)
{
ISession session = Session.HybridSessionBuilder.Instance;
IList<Test> ret = null;
ITransaction tx = null;
tx = session.BeginTransaction();
ret = session.CreateCriteria(typeof(Test))
.Add(Expression.Eq("ID", testId))
.List<Test>();
tx.Commit();
return ret.Count == 0 ? null : ret[0];
}
public static void Save<T>(T element)
{
ISession session = Session.HybridSessionBuilder.Instance;
ITransaction tx = null;
tx = session.BeginTransaction();
session.Save(element);
tx.Commit();
}
public static void Delete<T>(T element)
{
ISession session = Session.HybridSessionBuilder.Instance;
ITransaction tx = null;
tx = session.BeginTransaction();
session.Delete(element);
tx.Commit();
}
}
Then the nHibernate mapping XML:
<class name="Data.Test.Test, Data" table="test_info">
<id name="ID" column="testid">
<generator class="native" />
</id>
<property name="Name" />
<bag name="Related" table="test_relate" lazy="false" cascade="none">
<key column="testid"></key>
<one-to-many class="Data.Test.TestRelate, Data"></one-to-many>
</bag>
</class>
<class name="Data.Test.TestRelate, Data" table="test_relate">
<id name="ID" column="relateid">
<generator class="native" />
</id>
<property name="TestID" />
<property name="Data" />
</class>
And finally the code I'm having trouble with:
Data.Test.Test Test = new Data.Test.Test();
Test.Name = "Hello World";
Test.Save();
Data.Test.TestRelate Relate = new Data.Test.TestRelate(Test);
Relate.Data = "How are you?";
Relate.Save();
Test = Data.Test.Test.Get(Test.ID);
int Count = Test.Related.Count;
The problem is that the Test.Related
list is always empty when I run this code. However if I destroy the NHibernate session and load up the Test again it populates the list as expected. I realize I could probably flush all caching data but it seems like there should be a cleaner solution to this issue. Any suggestions?
Upvotes: 1
Views: 805
Reputation: 35117
Alright so I realized that my approach was due to some past failed attempts at utilizing NHibernate's cascading. I'll go over each one of the issues and what I did to resolve it.
Related
elements to a new Test
element because the TestID
value is not allowed to be null in the database. Altering the property from an integer type to the Test
type itself remedied this situation as NHibernate was able to populate the field value after saving the new Test
element. Related
record by removing it from the list would result in an error due to NHibernate attempting to Update the TestID
field to null prior to a delete. Adding the inverse="true"
attribute to the Bag
mapping element resolved this issue.Test
object would not delete the orphaned Related
records. Setting the cascade attribute to all-orphan-delete
remedied this.Here's all the new code (there were no changes to the Factory class):
public class Test
{
public static Test Get(int testId) { return Factory.GetTest(testId); }
public Test() { Related = new List<TestRelate>(); }
public virtual int ID { get; protected set; }
public virtual string Name { get; set; }
public virtual IList<TestRelate> Related { get; set; }
public virtual void Delete() { Factory.Delete(this); }
public virtual void Save() { Factory.Save(this); }
}
public class TestRelate
{
protected TestRelate() { }
public TestRelate(Test test) { Test = test; }
public virtual int ID { get; protected set; }
public virtual Test Test { get; set; }
public virtual string Data { get; set; }
public virtual void Delete() { Factory.Delete(this); }
public virtual void Save() { Factory.Save(this); }
}
Mapping changes:
<class name="Data.Test.Test, Data" table="test_info">
<id name="ID" column="testid">
<generator class="native" />
</id>
<property name="Name" />
<bag name="Related" table="test_relate" lazy="false" cascade="all-delete-orphan" inverse="true">
<key column="testid"></key>
<one-to-many class="Data.Test.TestRelate, Data"></one-to-many>
</bag>
</class>
<class name="Data.Test.TestRelate, Data" table="test_relate">
<id name="ID" column="relateid">
<generator class="native" />
</id>
<many-to-one name="Test" column="testid" />
<property name="Data" />
</class>
The following code now behaves as expected:
Data.Test.Test Test;
Data.Test.TestRelate Relate;
Test = new Data.Test.Test();
Test.Name = "Hello World";
Relate = new Data.Test.TestRelate(Test);
Relate.Data = "How are you?";
Test.Related.Add(Relate);
Test.Save();
Relate = new Data.Test.TestRelate(Test);
Relate.Data = "Relate #2";
Test.Related.Add(Relate);
Test.Save();
Test.Related.RemoveAt(0);
Test.Save();
Test = Data.Test.Test.Get(Test.ID);
int Count = Test.Related.Count;
Test.Delete();
I was able to glean most of these answers from http://ayende.com . I highly recommend this site as a resource for nHibernate questions.
Upvotes: 1
Reputation: 51224
NHibernate doesn't populate one-to-many collections automatically on commit. You should simply add TestRelate
instances to the Related
list, as you would do without NHibernate, and then (if you set a "cascade save" mapping) even commit the Test
instance only.
There is no need to use the TestID
property inside the program at all, as this property is actually only a byproduct of relational DB mapping.
Upvotes: 1
Reputation: 109109
When you do new Data.Test.TestRelate(Test)
there is nothing that adds the new TestRelate instance to the collection in the owner Test. (Unless you do that in the constructor, but I assume you only set TestId there).
You should Add()
the new TestRelate instance to Test.Related. Nhibernate will notice the change in the collection and save the new item when the session is flushed.
Upvotes: 1