Save multiple rows simultaneously from the same form - dotnet core

十年热恋 提交于 2019-12-04 09:24:05

This problem has 2 parts to it, the first is that there needed to be a way to edit collections of data. This can be solved with EditorTemplates, which involves creating a single editor model and then calling @Html.EditorFor(..) on the collection of items you wish to edit.

A minimal sample (Full Fx, not Core) is available on Github.

The second problem was with the way the entities were being updated, the property being changed was wrong and hence not saving (the PK was being updated to the PK) and the entity was being attached when it's already tracked.

foreach (var TbListId in tbMapViewModel.TBMapBalancesList)
{
    var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
    if (getCode != null)
    {
        getCode.TbMapId = TbListId.TbMapId;
    }
}
try
{
    _context.Update(tbMapViewModel.TBMapBalances);
    await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
    throw;
}

It's important to remember what Entity Framework does for you when you retrieve a model from the database. It is automatically tracked by the context, and so it's already attached and ready to update, anything you change will be automatically tracked and subsequently saved.

The call to _context.Update(..) tries to attach the non-tracked models (from your POSTed data) to the context based on ID, but because you've already pulled that ID out (in your .Where(..).FirstOrDefault(..)) it's already tracked, and so blows up.

Also given that this is EFC 1.0 and you have no .Find(..) method, using .SingleOrDefault(..) is probably a better method to use on a primary key field.

Your rewritten code could be as so:

foreach (var postedModel in tbMapViewModel.TBMapBalancesList)
{
    var dbModel = _context.TBMapBalances.SingleOrDefault(p => p.TbMapId == postedModel.TbMapId);
    if (dbModel != null)
    {
        dbModel.UniqueAdp = postedModel.UniqueAdp;
    }
}
await _context.SaveChangesAsync();

For posterity though I wouldn't recommend it for security reasons, if you wanted to attach your whole posted model to the context (based on ID) and update it, you can do so with code similar to your original, removing the foreach loop:

_context.UpdateRange(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();

(I don't recommend it because everything that was posted will then be set in the database, from experience it's advisable to only set the fields you're expecting to update as per the first code set. It should, however, be quicker than the foreach loop given that you're not loading from the database and saving back in, only the latter)

Do you have the inputs for the comments already built into the razor page? I do not see them. What you would want to do is create a form with the input types that you want for each item in the loop inside the loop. Each form would then reference the iterator as a hidden value to pass when posted. If the loop is foreach(var item in Model.items){} you would have a form containing the inputs in that block with a hidden input that looks like <input type="hidden" name="index" value="@item.index"/> This will allow you to post with whatever identifier you need to attach that specific form data to the correct model.

See this answer for the structure of the form inside the loop Multiple forms on one MVC form, created with a loop, only the first submits data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!