Imagine the following scenario:
Townships has two m:n collections mapped to Region. My Controller has special actions for updating these collections, while there is a generic Edit method that takes care of updating normal properties on Township. The code in question looks quite innocent:
[HttpPost] public virtual ActionResult Edit([Bind]T item) { if (!ModelState.IsValid) return View(item); using (var trans = session.BeginTransaction()) { session.Update(item); trans.Commit(); } return RedirectToAction("List"); }
Well, the problem is quickly found using NHProf:
Whenever I updated the Township entity all it’s associated Regions where cleared.
Turns out, the problem lies with the ModelBinder in MVC2: Since it reconstructs a new Township item and populates it with values from the request, there is no way for MVC to fill the WinterRegions and SummerRegions collection. So NHibernate got empty collections and assumed I removed all items from them and decided to persist that removal to the database, resulting in a DELETE.
There are two solutions to the problem: a) turn off Cascade.All b) Fill the collections before the update.
Since I already used the Cascade Behavior in other places I decided to go with b and select the entity prior to updating it. The resulting code looks like this:
[HttpPost] public override ActionResult Edit([Bind]Township item) { using (var trans = session.BeginTransaction()) { var township = session.Get<Township>(item.Id); session.Evict(township); item.WinterRegions = township.WinterRegions; item.SummerRegions = township.SummerRegions; session.Update(item); trans.Commit(); } return RedirectToAction("List"); }
Notice that it is important to first evict the fetched entity from the session, otherwise you’ll get an Exception stating that the same identified is already associated with this session cache.
To be honest: I don’t feel particularly fond of this solution, if anyone can point out a better solution please leave a comment or email me. While at it, it would be nice to be able to change the cascade behavior of entities for one session (like FetchMode for one criteria).