views:

169

answers:

4

Let's say I have a DB table with columns A and B and I've used the Visual Studio designer to create a Linq objects for this table. All fields are marked NOT NULL.

Now I'm trying to edit this record using typical MVC form editing and model binding, but field B doesn't need to be editable, so I don't include it in the form.

When the post handler binds the object it doesn't populate field B, leaving it null. Now when I save the object I get a DB error saying field B can't be NULL.

The code to save looks something like:

m_DataContext.MyObjects.Attach(myObject);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues, myObject);
m_DataContext.SubmitChanges();

How do I get this to work? Do I need to include field B as a hidden field on the form - I don't really want to do this as it may be updated by other users at the same time and I don't want to stomp over it.


I've found the solution to this problem revolves around getting the entity object associated with the data context before applying the changes. There's a couple of ways of doing this which I've described in separate answers below.

A: 

You could add a timestamp field and check one on the page with the one in the DB (hiding the timestamp field as well). If a user has updated the record, a concurrency error is returned and the page is refreshed, or left the same iwth the users changes.

Russell
I got the impression he wants to *allow* concurrent edits across different fields.
JoshJordan
Yes, I want to allow concurrent edits and I'm interested in how to do this correctly from a Linq point of view. If I was using straight SQL this would be easy, surely it can be done with Linq somehow.
cantabilesoftware
Ok well I am not sure of a way to do it in Linq.
Russell
A: 

Use a Custom Model Binder

This approach uses a custom model binder to create the entity object and associate with the data context, before the binding takes place.

public class MyObjectBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        MyObject a = ((MyObjectController)controllerContext.Controller).Repository.GetMyObjectForUpdate(bindingContext.ValueProvider["ID"].AttemptedValue.ToString());
        return a;
    }
}

The repository then creates the object and associates it with the data context:

public Object GetMyObjectForUpdate(string id)
{
    MyObject o=new MyObject();
    o.ID=id;
    m_DataContext.Articles.Attach(o);
    m_DataContext.Refresh(RefreshMode.KeepCurrentValues);
    return o;
}

The action handler needs to be attributed to use the model binder...

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditMyObject([ModelBinder(typeof(MyObjectBinder))] MyObject o)
{
    if (!ModelState.IsValid)
        return View("EditMyObject", a);

    Repository.SaveMyObject(a);
    return RedirectToAction("Index");
}

and finally SaveMyObject simply calls datacontext.SubmitChanges().

For this to work I also needed to set the update check attributes on all columns to Never (in the dbml file).

Overall, this approach works but is messy.

cantabilesoftware
A: 

Use Two Entity Objects

This approach uses two entity objects, one for the model binding and one LINQ:

public override void SaveMyObject(MyObject o)
{
 // Create a second object for use with linq and attach to the data context
    MyObject o2 = new MyObject();
    o2.ID = o.ID;
    m_DataContext.Articles.Attach(o2);
    m_DataContext.Refresh(RefreshMode.KeepCurrentValues);

 // Apply fields edited by the form
    o2.A = o.A;

    // Submit
    m_DataContext.SubmitChanges();
}

This approeach doesn't require any special handling in the controller (ie: no custom model binding) but still requires the Update Check property to be set to Never in the dbml file.

cantabilesoftware
+2  A: 

Descend into SQL

This approach ditches LINQ in favour of straight SQL:

public override void SaveMyObject(MyObject o)
{
    // Submit
    m_DataContext.ExecuteCommand("UPDATE MyObjects SET A={0} WHERE ID={1}", o.ID, o.A);
}

I like this approach the best because of it's simplicity. As much as I like LINQ I just can't justify it's messiness with this problem.

cantabilesoftware