0

I'm still learning a lot about The Entity framework and how it communicates, so this is probably something simple, but I cannot seem to figure out what the problem is.

To set the scene, I have a working Create function generated by Visual Studio, and I have set the values in this function as follows:

myObject.Id = Guid.NewGuid().ToString();
myObject.dateCreated = DateTime.Now;

This looks great, and everything is working as it should. The problem comes when I then try to edit the record.

I have this model:

public class MyObject
{
    public string Id { get; set; }
    public string UserId { get; set; }
    public string Name { get; set;  }
    public DateTime dateCreated { get; set; }
    public Nullable<DateTime> dateModified { get; set; }
    public Boolean aDefault { get; set; }

}

(For the most part auto generated) these controllers:

public async Task<ActionResult> Edit(string id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    MyObject myObject = await db.myObjects.FindAsync(id);
    if (myObject == null)
    {
        return HttpNotFound();
    }
    return View(myObject);
}

public async Task<ActionResult> Edit([Bind(Include = "Id,aDefault,Name")] MyObject myObject)
{
    if (ModelState.IsValid)
    {
        myObject.dateModified = DateTime.Now;
        db.Entry(myObject).State = EntityState.Modified;
        await db.SaveChangesAsync(); //db is the generated dbcontext
        return RedirectToAction("Index");
    }
    return View(myObject);
}

And (only displaying the fields I have on the form) the view:

@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(model => model.Id)

<div class="form-group">
    @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
    </div>
</div>


<div class="form-group">
    @Html.LabelFor(model => model.aDefault, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        <div class="checkbox">
            @Html.EditorFor(model => model.aDefault)
            @Html.ValidationMessageFor(model => model.aDefault, "", new { @class = "text-danger" })
        </div>
    </div>
</div>

So currently when I run this code and go to the edit area, I'm expecting it to take the Name and default values, post them to the controller, and have that only modify the relevant fields in the database. However, currently when I run this code, I get:

The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value. The statement has been terminated.

This caused me to believe my dateCreated was being set to a null. I confirmed this by making it a Nullable and it no longer threw the exception, but of course set my dateCreated in the database to a null.

Do I need to bind all properties to the view similar to how the Id was set in a hidden field? I'd hope not as I figure I only need to bind the relevant items. Is there a way I can exclude these fields that are set once to not be updated?

Reisclef
  • 2,056
  • 1
  • 22
  • 25
  • 1
    The correct approach is to get the data model based on the ID in the POST method and then update the relevant properties –  Sep 21 '15 at 13:08
  • 1
    The problem is that the framework is not able to tell the difference between a missing value and a null value. Microsoft has come up with support for this by using Delta: http://stackoverflow.com/questions/14177676/whats-the-currently-recommended-way-of-performing-partial-updates-with-web-api – Khanh TO Sep 21 '15 at 13:12
  • In javacript, we have both undefined and null so that we are able to tell whether the value is missing or empty while we have only null in c# (we cannot tell whether we want to update with a null value or don't want to update that field) – Khanh TO Sep 21 '15 at 13:26
  • @StephenMuecke Thanks! So I take it the way you're suggesting would be to pass through the model as an argument i.e. Edit(MyObject model)? – Reisclef Sep 21 '15 at 13:29
  • @KhanhTO Thanks for that link, I'll consider using that, though it's a bit more involved than I'd expect! – Reisclef Sep 21 '15 at 13:29
  • 1
    Actually I would recommend you use a view model that contains only the 3 properties (`Id`, `Name` and `aDefault`) and use that as your parameter without the `[Bind]` attribute. (note the hidden input is not really required since it's value will be added as a route parameter assuming you using the default route) –  Sep 21 '15 at 13:33

1 Answers1

0

Thanks to the comments, and a bit of research over the past 24 hours, I realised that the major issue was one that the Entity Framework generated for me. This line was causing me the most headaches:

db.Entry(myObject).State = EntityState.Modified;

This set all properties to modified, and therefore set the values to nulls for everything, and everything that wasn't bound stayed as a null.

I can see why another option (perhaps better practice) might be to add another ViewModel for only the necessary properties in the view, but I figured in this case, the controller might be better placed to implement what is changing. Here is my modified post method:

public async Task<ActionResult> Edit([Bind(Include = "Id,aDefault,Name")] MyObject myObject)
{
    if (ModelState.IsValid)
    {
        db.MyObjects.Attach(myObject); //MyObjects is the DbSet
        myObject.dateModified = DateTime.Now;
        db.Entry(myObject).Property(o => o.Name).IsModified = true;
        db.Entry(myObject).Property(o => o.aDefault)IsModified = true;
        await db.SaveChangesAsync(); //db is the generated dbcontext
        return RedirectToAction("Index");
    }
    return View(myObject);
}
Reisclef
  • 2,056
  • 1
  • 22
  • 25