0

Why is my ASP.NET MVC3 Edit action being given a new object instead of the object to edit?
When I inspect the properties of the object that's given the ID value is the unsaved-value I've specified in my NHibernate mapping files.

In order to work around this I can query back the object to update from the database and set the properties I want from the object that's passed to the method. But while this works OK for a really simple object with one or two properties it quickly becomes a problem when there are lots of properties and it seems like I'm fighting the system (which, to me, is an indication I'm not doing things the way they're supposed to be done).

My mapping:

<id name="Id" column="Id" type="Guid" 
    unsaved-value="00000000-0000-0000-0000-000000000000">

My Edit method:

[NHibernateActionFilter]
[HttpPost]
public ActionResult Edit(Status status, Guid id)
{
    try
    {
        if (ModelState.IsValid)
        {
            var originalStatus = Session.QueryOver<Status>()
                                        .Where(s => s.Id == id)
                                        .SingleOrDefault<Status>();
            if (originalStatus == null)
                throw new Exception(string.Format("No Status with Id: {0}.", id));

            originalStatus.StatusName = status.StatusName;
            Session.Update(originalStatus );

            return RedirectToAction("Index");
        }
    }
    catch (DataMisalignedException ex)
    {
        // TODO: Log4Net this
        ModelState.AddModelError("", "Unable to save changes. Please try again.");
    }
    return View(status);
}

*The NHibernateActionFilter is taken from a series of posts by Ayende on Refactoring toward frictionless & odorless code and gives an ISession to each action in the controller decorated with it. However I don't think that's the cause of the problem.

My problem manifests when trying to edit an item, a StaleObjectException is thrown:

Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [TheWorkshop.Web.Models.Status#00000000-0000-0000-0000-000000000000]

the new Guid shows me that the Status mentioned is new and not the object previously returned in order to populate the details view. So it would seem that a new object has been newed up and passed into my Edit ActionResult, but that's not helpful.

As I've shown above I can code around it, but that's more of a hack than a solution. I would prefer to be able to leave my Edit method like so:

[NHibernateActionFilter]
[HttpPost]
public ActionResult Edit(Status status)
{
    try
    {
        if (ModelState.IsValid)
        {
            Session.Update(status);

            return RedirectToAction("Index");
        }
    }
    catch (DataMisalignedException ex)
    {
        // TODO: Log4Net this
        ModelState.AddModelError("", "Unable to save changes. Please try again.");
    }
    return View(status);
}

Edit
I have looked at the answer How to handle update entities. NHibernate + ASP.NET MVC but that does pretty much what I've already done and I can't believe the update methods have to be so messy as my attempt.

Also I've looked at what does this error mean in nhibernate; it looks identical to my problem, but I couldn't get any of the answers to work for me, that is the problem still persists.

Community
  • 1
  • 1
Simon Martin
  • 4,203
  • 7
  • 56
  • 93

2 Answers2

3

The Status instance provided to the action method is generated by MVC's model binder - it's a new object because the model binder and NHibernate knows nothing about each other. To make it have the correct ID, you will need to provide the ID as part of the form (hidden). But take care so that the user cannot make unauthorized changes by posting more fields than the form contains, or by changing values of hiddens fields.

It's common to have different models. The MVC uses view models in the front end, which have a different purpose and won't necessarily look the same as your domain models. The action method is responsible for acting on the values in the view model and perform the correct action on the domain model.

Oskar Berggren
  • 5,583
  • 1
  • 19
  • 36
  • Ahh... well there **is** a hidden field in my form for the Id, however Id is a `protected` field defined on an abstract base class and there is no setter mechanism. The idea being that nothing should arbitrarily be able to change an entity's Id value. So I'm guessing that now what's happening is that the MVC model binder is unable to set the Id value on the object it's creating from the form controls. – Simon Martin Mar 06 '13 at 11:14
  • Sometimes I use a pattern where the ID can only be set if it isn't already set. Usually this is similar to `if (_id == 0 (or null)) throw InvalidOperationException(...);` – Oskar Berggren Mar 06 '13 at 15:19
0

I can't really add anything to Oskers answer which should be marked as correct. Just a couple of points.

In your original method as Ayende's attribute creates a transaction, you do not need the update statement, NHibernate will see that the attribute has changed and update it for you.

If you feel that the second method is the way to go you can achieve this by creating a custom model binder, which would load in the Nhibernate object.

see: ASP.NET MVC model binding foreign key relationship

Community
  • 1
  • 1
  • I've experimented Jimmy Bogard's custom model binder, but I haven't really used it in any real code. http://lostechies.com/jimmybogard/2011/07/07/intelligent-model-binding-with-model-binder-providers/ – A Bunch Mar 04 '13 at 21:08