Tigraine
Daniel Hoelbling talks about programming

NHibernate removes items from Many-To-Many upon Update of Entity due to Model Binding

June 19th, 2010 . by Daniel Hölbling

Imagine the following scenario:

nh

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:

image

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).


  • http://twitter.com/mookid8000 Mogens Heller Grabe

    IMO your “real problem” is that you are letting a model binder deserialize directly into your domain entities. I think you would benefit from creating separate models to receive form posts, and then let some code fetch the right entity and call some methods on it. This way, you will not run into weird issues like the one you mention.

  • http://www.tigraine.at Daniel Hölbling

    Agreed, I do have Dtos for some of my models that I use, but in this case it was just simpler to do it this way :)
    And deserialization of the model binder works for 90% of the entities pretty well..

  • Chris J Owen

    I flick between agreeing and disagreeing that you should pass View Models in and DTOs out. In theory it should be what you do, though in practice its a real PITA, especially if your domain is simple and your DTOs look remarkably like your domain objects. I totally agree that it would be nice to change the cascade mode on a per session basis, in fact I would like to be able to do it per session tracked domain object instance.

    One thing that did slightly confuse me here, is how are you managing session, because if you a session per request then how is your item even bound to the session when after model binding?

  • Svante

    I like to:

    public ActionResult Edit(int id, FormCollection collection) {
    var item = session.Get<Township>(id);
    UpdateModel<Township>(Township);

    or rather having an interface with bindable members that the model implements:

    public ActionResult Edit(int id, FormCollection collection) {
    var item = session.Get<Township>(id);
    UpdateModel<ITownshipBindable>(Township);

  • http://www.discount-air-jordan.com nike air jordan

    Mark S. is definitely on the right track. If you want to get a professional looking email address, Id recommend buying your name domain name, like or
    air jordan 17.5
    If its common it might be difficult to get, however, be creative and you can usually find something.

  • http://www.moncler-down-jackets.com louis vuitton outlet

    I've been looking for a similar to this post. Not only extensively but also detailly. We can learn a lot from the post. moncler jackets I recommend to you , ugg boots sale you can come communication in here. Let us grow up together.On the other hand ,I know some websites content is very well.you can go and see.Such asugg boots for sale

  • http://www.moncler-down-jackets.com louis vuitton outlet

    Thank you very much for sharing!!To top!louis vuitton outlet is a professional webside which offer lots of common sense of life and I have learnt a lot since my friends recommended it to me.There are several articles about this theme in Louis vuitton bags,come on and have a discussion! air jordan 7