Search code examples
c#asp.net-mvc-3drop-down-menueditviewbag

ASP.NET MVC3 ActionResult Edit doesn't save changes in database


I have a problem with editing data in the controller. My two models look like this:

[Table("Zielgruppen")]
public class Zielgruppe
{
    public int Id { get; set; }
    public string Zielgruppenname { get; set; }
    public Bezug Bezug { get; set; }
}

and

public class Bezug
{
    public int Id { get; set; }
    public string Bezugsname { get; set; }
}

The functions in my controller are:

public ActionResult Edit(int id)
{
    Zielgruppe zielgruppe = _db.Zielgruppe.Include("Bezug").Single(z => z.Id == id);
    ViewBag.BezugsId = new SelectList(_db.Bezug, "Id", "Bezugsname", zielgruppe.Bezug.Id);
    return View(zielgruppe);
}

[HttpPost]
public ActionResult Edit(Zielgruppe aktualisierteZielgruppe)
{
    if(ModelState.IsValid)
    {
        aktualisierteZielgruppe.Bezug = _db.Bezug.Find(aktualisierteZielgruppe.Bezug.Id);
        _db.Entry(aktualisierteZielgruppe).State = EntityState.Modified;
        _db.SaveChanges();
        return RedirectToAction("Index");
    }
    ViewBag.BezugsId = new SelectList(_db.Bezug, "Id", "Bezugsname", aktualisierteZielgruppe.Bezug.Id);
    return View();            
}

My Problem is that if i change aktualisierteZielgruppe.Bezug the changes won't get save in the database.

and this is my edit.cshtml:

@model Medien_Archiv.Models.Zielgruppe

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Zielgruppe</legend>

        @Html.HiddenFor(model => model.Id)

        <div class="editor-label">
            @Html.LabelFor(model => model.Zielgruppenname)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Zielgruppenname)
            @Html.ValidationMessageFor(model => model.Zielgruppenname)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Bezug)
        </div>
        <div class="editor-field">
            @Html.DropDownListFor(model => model.Bezug.Id, ViewBag.BezugsId as SelectList)   
        </div>
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Solution

  • This is one of the cases where entity framework falters - tracking updated associations.

    I'm assuming that you're not doing any fancy model binding here (ie, model binder pulling from the database).

    if(ModelState.IsValid)
    {
        aktualisierteZielgruppe.Bezug = _db.Bezug.Find(aktualisierteZielgruppe.Bezug.Id);
    
        // The problem is here.  EF doesn't mark associations as modified
        // the same way it tracks scalar types.
        _db.Entry(aktualisierteZielgruppe).State = EntityState.Modified;
        _db.SaveChanges();
        return RedirectToAction("Index");
    }
    

    Here's the more conventional way:

    [HttpPost]
    public ActionResult Edit(int id, FormCollection collection)
    {
        // Get the original record to edit from the database.
        var gruppe = _db.Zielgruppe.Include("Bezug").Single(z => z.Id == id);
    
        // This will attempt to do the model binding and map all the submitted 
        // properties to the attached entity.
        if (TryUpdateModel(grupppe))
        {
            _db.SaveChanges();
            return RedirectToAction("Index");
        }
    }
    

    Also note that this method of binding to entities leaves you open to over-posting attacks. You can mitigate this attack vector by white-listing model properties during model binding, or bind against a view model and then map.