I have a simple application where users can open orders, make changes to them, then save them back to the database. When an order is opened, I retrieve it from the database like this
ISession session = SessionFactory.OpenSession();
...
Order order = session.Query<Order>()
.Where(o => o.Id == id)
.FirstOrDefault();
To enable lazy loading of order.Comments, I keep the session open until the order is closed. Here are the mappings:
<class name="Order">
<id name="Id">
<generator class="identity" />
</id>
...
<set name="Comments" access="field.camelcase-underscore" cascade="all-delete-orphan" lazy="true" order-by="Created">
<key column="OrderId" />
<one-to-many class="Comment" />
</set>
</class>
<class name="Comment" table="OrderComment" lazy="false">
<id name="Id">
<generator class="identity" />
</id>
<many-to-one name="Author" />
<property name="Created" />
<property name="Text" length="1000" />
</class>
The app is designed so that while an order is open, it can be saved several times before it is closed. I save like this:
using (ITransaction trans = session.BeginTransaction())
{
session.SaveOrUpdate(order)
trans.Commit();
}
Finally, when the user closes the order, I dispose of the session.
Here’s there problem: If the user adds a comment, saves, and then before closing the order, adds another comment and saves again, the first comment is removed during the second save. Here’s the sql that is outputted from the second save:
NHibernate: INSERT INTO OrderComment (Id, Author, Created, Text) VALUES (hibernate_sequence.nextval, :p0, :p1, :p2) returning Id into :nhIdOutParam;:p0 = 1 [Type: Int32 (0)], :p1 = 14.01.2013 12:53:20 [Type: DateTime (0)], :p2 = '2' [Type: String (0)], :nhIdOutParam = NULL [Type: Int32 (0)]
**NHibernate: UPDATE OrderComment SET OrderId = null WHERE OrderId = :p0 AND Id = :p1;:p0 = 465 [Type: Int32 (0)], :p1 = 591 [Type: Int32 (0)]**
NHibernate: UPDATE OrderComment SET OrderId = :p0 WHERE Id = :p1;:p0 = 465 [Type: Int32 (0)], :p1 = 592 [Type: Int32 (0)]
So the problem is that line in bold – the OrderId for the first comment is being set to null. Can anybody tell my why?
Is there anything wrong with the way I’m using nHibernate here? To reiterate what I do:
- Open a session
- Retrieve an object
- User updates the object
- Save the object by beginning a transaction, calling session.SaveOrUpdate(object), then committing the transaction.
- Repeat steps 3 and 4 any number of times.
- Dispose of the session.
Is this an acceptable way to use nHibernate?
Edit
Here is the comments property in the Order class:
ICollection<Comment> _comments = new List<Comment>();
public virtual ReadOnlyCollection<Comment> Comments
{
get
{
return _comments.ToList().AsReadOnly();
}
}
Then comments are added by calling the following method:
public virtual void AddComment(Comment comment)
{
_comments.Add(comment);
}
...
Comment comment = new Comment()
{
Author = User.Current,
Created = DateTime.Now,
Text = text
};
order.AddComment(comment);
Here is the Comment class. Id is implemented in the base class PersistentObject<T>:
public class Comment : PersistentObject<int>
{
public User Author { get; private set; }
public DateTime Created { get; private set; }
public string Text { get; private set; }
}
public abstract class PersistentObject<T>
{
public virtual T Id { get; protected internal set; }
public override bool Equals(object obj)
{
// If both objects have not been saved to database, then can't compare Id because this
// will be 0 for both. In this case use reference equality.
PersistentObject<T> other = obj as PersistentObject<T>;
if (other == null)
return false;
bool thisIsDefault = object.Equals(Id, default(T));
bool otherIsDefault = object.Equals(other.Id, default(T));
if (thisIsDefault && otherIsDefault)
return object.ReferenceEquals(this, other);
else if (thisIsDefault || otherIsDefault)
return false;
else
return object.Equals(this.Id, other.Id);
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}
I don’t think that the
HashCodeshould be taken from theId.Order.Commentsis a set, which is base on the hash. The comments all have an Id0at the beginning. TheHashCodeis taken when adding to the collection, when it is0.