简体   繁体   中英

Updating complex model binding EF5 ASP.NET MVC4

I'm having a difficult time figuring out how to update my entities and their related data. Using Lazyloading..

I have the following entity models

public class Config
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Int32 Id { get; set; }

    [Required]
    [MaxLength(100)]
    public String Name { get; set; }

    public virtual IList<DataField> DataFields { get; set; }
}

public class DataField
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Int32 Id { get; set; }

    [ForeignKey("Config")]
    public Int32 ConfigId { get; set; }

    public virtual Config Config { get; set; }

    [Required, MaxLength(1000)]
    public String Name { get; set; }
}

With the corresponding view models. I've stripped them down, removed validations and such.

public class ConfigViewModel
{
    public Int32 Id { get; set; }

    public String Name { get; set; }

    public IList<DataFieldViewModel> DataFields { get; set; }

    public ConfigModel()
    {
        DataFields = new List<DataFieldViewModel>();
    }
}
public class DataFieldViewModel
{
    [Required]
    public Int32 Id { get; set; }

    public String Name { get; set; }
}

In my Edit.cshtml form I dynamically add new datafields, and when I post the form, they are properly deserialised to ConfigViewModel.DataFields. So long everything is working. But how do I convert these models, and update the entitymodels? If I post new datafields, their id's will be 0, and they should be added, but the ones that already have an Id, should be updated.. I don't know how to do this, and can't find anything related, or that I could understand.

I have the following in my ConfigController.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(ConfigViewModel model)
{
    try
    {       
        if (!ModelState.IsValid)
            return View();

        var config = uow.Repository<Entity.Models.Config>().FindById(model.Id);

        config.Name = model.Name;

        // Do something with the datafields
        // config.DataFields

        uow.Repository<Entity.Models.Config>().Edit(config);
        uow.Save();

        return RedirectToAction("Index");
    }
    catch(Exception ex)
    {
        ModelState.AddModelError("Error", ex.Message);
        return View(model);
    }
}

In my repository I have:

public void Edit(TEntity entity)
{
    var entry = Context.Entry<TEntity>(entity);
    entry.State = EntityState.Modified;
}

My Edit.cshtml form looks like

@for(var i = 0; i < Model.DataFields.Count; i++)
{
    <tr>                         
        <td>@Html.HiddenFor(m => m.DataFields[i].Id)</td>
        <td>@Html.TextBoxFor(m => m.DataFields[i].Name)</td>
        <td>@Html.EnumDropDownListFor(m => m.DataFields[i].Type)</td>
    </tr>
}

It looks like a couple of things are happening here.

If I understand your data structures correctly, you have a Config object that has zero or more DataField objects associated with it.

The Edit page of your application for editing Config objects allows you to add new DataField items or modify existing DataField items.

I'm assuming that in the commented section of your example:

// Do Something with the DataFields
// config.DataFields

that you're translating the View Models back to your domain objects.

Now I'm going to assume that you are using a per-request lifetime for the DbContext , as that's most typical in these scenarios. So, on each web request, a new DbContext is instantiated as part of the instantiation chain of the MVC Controller and its dependencies (eg services, repositories, unit of work, etc.). So on this new request for editing Config objects, the DbContext is empty—it has no knowledge of any objects because it's a brand new DbContext .

At this point, you need to Attach the Config entity to the DbContext so that the DbContext will start tracking it. If anything changed about the Config entity (eg was the name changed, or were new DataField objects added to the collection?), you will need to set the state of the entity within the DbContext to Modified (this you have done in your example above).

Attaching the Config entity to the DbContext will also result in attaching all the related DataField objects that are referenced by the edited Config entity. But there's a slight wrinkle. You will need to tell the DbContext which entities are new, and which are modified. You can easily tell new entities from modified entities because the DataField.Id property will be 0. But how do you tell if an existing DataField entity has been returned unchanged? This is important, because simply attaching the DataField entities to the DbContext puts them in an Unmodified state within the context. So, if there were any changes on an existing entity, those changes would not be persisted when committing the context.

A naive approach to solve this problem would entail setting all DataField entities whose Id property is non-zero to the Modified state. This will increase load on the database (but for a small application, this will be negligible). However, if you have any triggers or some other mechanism for auditing when records are created or updated, this is not a good solution. Assuming you are not performing auditing, the naive approach may work well:

public void Edit(TEntity config)
{
    Context.Attach<TEntity>(config);
    Context.Entry<TEntity>(config).State = EntityState.Modified;

    foreach(var df in config.DataFields)
    {
        Context.Entry<DataField>(df).State = EntityState.Modified;
    }

    // I noticed you never saved the changes to the DbContext. Do you need
    // to do this here, or are you doing it with your UOW somewhere else??
    Context.SaveChanges();
}

Again, this is a naive approach that should generally work well for small applications. At the very least, it should give you a better idea of the kinds of things you need to be aware of when working with Entity Framework in a disconnected N-Tier scenario.

Also, I highly recommend you check out the following books:

Each of those books discusses the scenario you're talking about. You may want to start with the Code First book since you are using code first.

HTH

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM