简体   繁体   中英

Updating two related Objects using generic repository pattern and Entity Framework

I am using a generic repository and Entity Framework. I can update one of the classes normally, but I'm having trouble updating the relationship between them.

I'm also using lazy loading, AutoMapper and a service layer to isolate the domain.

public class DetalhesDoArquivoViewModel
{
    public DetalhesDoArquivoViewModel()
    {
        Id = Guid.NewGuid();
    }

    [Key]
    public Guid Id { get; set; }

    public string FileName { get; set; }

    public string Extension { get; set; }

    public Guid FormularioId { get; set; }

    public virtual FormularioDoUploadViewModel DescricaoDoUpload { get; set; }
}

public class FormularioDoUploadViewModel
{
    public FormularioDoUploadViewModel()
    {
        Id = Guid.NewGuid();
    }

    [Key]
    public Guid Id { get; set; }

    [Required(ErrorMessage = "Digite um nome")]
    [Display(Name = "Nome")]
    [MaxLength(100)]
    public string Nome { get; set; }

    [Required(ErrorMessage = "Entre com uma descrição")]
    [Display(Name = "Descrição")]
    [MaxLength(500)]
    public string Descricao { get; set; }

    public virtual IEnumerable<DetalhesDoArquivoViewModel> DetalhesDoArquivo { get; set; }
}

My Update repository

public virtual TEntity Atualizar(TEntity obj)
{
        var entry = Db.Entry(obj);
        Dbset.Attach(obj);
        entry.State = EntityState.Modified;

        SaveChanges();
        return obj;
}

My service class:

public class UploadAppServices : BaseService, IUploadServices
{
    private readonly IFormularioUploadRepository _formularioUploadRepository;
    private readonly IDetalhesDoArquivoRepository _detalhesDoArquivoRepository;

     // Update
     public FormularioDoUploadViewModel Atualizar(FormularioDoUploadViewModel formularioDoUploadViewModel)
    {
        var form = Mapper.Map<FormularioUpload>(formularioDoUploadViewModel);
        _formularioUploadRepository.Atualizar(form);
        Commit();
        return formularioDoUploadViewModel;
    }

    //getById
    public FormularioDoUploadViewModel ObterPorId(Guid id)
    {
        return Mapper.Map<FormularioDoUploadViewModel>(_formularioUploadRepository.ObterPorId(id));
    }
}

My controller:

public class FormularioDoUploadController : BaseController
{
    private ApplicationDbContext db = new ApplicationDbContext();

    private IFormularioUploadRepository _formularioUploadRepository;
    private IUploadServices _uploadServices;

    public ActionResult Edit(Guid id)
    {         
        var formularioDoUploadViewModel = _uploadServices.ObterPorId(id);

        if (formularioDoUploadViewModel == null)
        {
            return HttpNotFound();
        }

        return View(formularioDoUploadViewModel);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(FormularioDoUploadViewModel formularioDoUploadViewModel)
    {
        if (ModelState.IsValid)
        {
            for (int i = 0; i < Request.Files.Count; i++)
            {
                var file = Request.Files[i];

                if (file != null && file.ContentLength > 0)
                {
                    var fileName = Path.GetFileName(file.FileName);

                    DetalhesDoArquivoViewModel detalhesDoArquivo = new DetalhesDoArquivoViewModel()
                    {
                        FileName = fileName,
                        Extension = Path.GetExtension(fileName),
                        FormularioId = formularioDoUploadViewModel.Id,
                    };

                    var path = Path.Combine(Server.MapPath("~/App_Data/Upload/"), detalhesDoArquivo.Id + detalhesDoArquivo.Extension);
                    file.SaveAs(path);
                }

                // Update
                _uploadServices.Atualizar(formularioDoUploadViewModel);
                return RedirectToAction("Index");
            }
        }

        return View(formularioDoUploadViewModel);
    }

Automapper is great for mapping entity to view-model, but I would avoid using it to map from a view-model to entity. This may seem convenient, but you are effectively unconditionally trusting the data received from the client and overwriting your database data. This means you have to send 100% of your entity domain model to the client, revealing more about your domain structure than you need to, and then accept that expanded domain model which can contain alterations that your client application does not intend to make. (intercepting the post to the server in the browser debugger and altering values in the object posted back to the server)

Submit actions should be coded to:

  • Validate that the current session user has permission to modify the record(s) identified by the submit request.
  • Limit the update to specific values provided in the request.
  • Validate those specific values.
  • Disconnect the user session and notify administrators if any of the above is violated.

In some cases, such as adding a new entity, the payload will effectively be a complete entity and potentially some related details. This still needs to be validated against the known data state. In other cases where you provide an action that updates an entity, the model posted back should merely contain the ID of the entity being updated, and the specific values the client is allowed to update. (not the entire, modified entity)

By passing entities, or view models that map directly to entities for a method intended to update some aspects of the entity, I can:

  • Re-assign that entity to someone else.
  • Use the request to attempt to assign another random entity to myself.
  • Negate or otherwise change any and all data recorded in that entity.

Do not trust anything received from the client.

This issue also presents a concurrent access issue where your system is adopting a "last in wins" scenario. Between the time you provided the entity/view model and the time you submit the view model back to the server, that entity data may have changed. By mapping the data into a new entity class, attaching, marking modified, and saving, you overwrite the data without any consideration as to whether the data was stale.

To avoid the issue you are seeing, and the security/stale issues, you should load the entity from the context on the Update post call, validate the authorization for the current user, check the row version # or timestamp to ensure the record isn't stale, validate your updated details, then, once you're absolutely sure that the data in your view model presents no risk to your entity, you can use automapper's .Map(source, detination) to copy the values across. If you need to update related entities against related view models, then as long as you .Include() those related entities when you retrieve the entity from the context, then the .Map() call should handle the related data.

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