使用通用存储库模式和实体框架更新两个相关的对象

时间:2019-03-25 20:31:43

标签: c# asp.net-mvc entity-framework repository

我正在使用通用存储库和实体框架。我可以正常更新其中一个类,但是在更新它们之间的关系时遇到了麻烦。

我还使用延迟加载,AutoMapper和服务层来隔离域。

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; }
}

我的更新存储库

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

        SaveChanges();
        return obj;
}

我的服务等级:

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));
    }
}

我的控制器:

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);
    }

1 个答案:

答案 0 :(得分:0)

Automapper非常适合将实体映射到视图模型,但是我会避免使用它来将视图模型映射到实体。这似乎很方便,但是实际上您是无条件地信任从客户端收到的数据并覆盖数据库数据。这意味着您必须将100%的实体域模型发送给客户端,比您需要的域结构更多的信息,然后接受扩展的域模型,其中可能包含客户端应用程序不打算进行的更改。 (在浏览器调试器中拦截发布到服务器的发布,并更改发布回服务器的对象中的值)

提交动作应编码为:

  • 验证当前会话用户是否有权修改提交请求所标识的记录。
  • 将更新限制为请求中提供的特定值。
  • 验证这些特定值。
  • 断开用户会话的连接,并在违反以上任何条件时通知管理员。

在某些情况下,例如添加新实体,有效负载将有效地成为完整的实体,并可能包含一些相关的详细信息。这仍然需要针对已知数据状态进行验证。在其他情况下,如果您提供了更新实体的操作,则回发的模型应仅包含要更新的实体的ID,以及允许客户端更新的特定值。 (不是整个修改后的实体)

通过传递实体,或通过查看直接映射到实体的模型以获取旨在更新实体某些方面的方法,我可以:

  • 将该实体重新分配给其他人。
  • 使用该请求尝试为自己分配另一个随机实体。
  • 否定或更改该实体中记录的所有数据。

不信任从客户端收到的任何信息。

此问题还提出了一个并发访问问题,您的系统正在采用“后赢”方案。在您提供实体/视图模型的时间与将视图模型提交回服务器的时间之间,该实体数据可能已更改。通过将数据映射到新的实体类,附加,标记已修改和保存,您可以覆盖数据而无需考虑数据是否陈旧。

为避免出现您遇到的问题以及安全/过时的问题,应从Update调用中的上下文中加载实体,验证当前用户的授权,检查行版本号或时间戳以确保记录不是陈旧的,请验证更新的详细信息,然后,一旦您完全确定视图模型中的数据对您的实体没有任何风险,则可以使用自动映射器的.Map(source, detination)在其中复制值。如果您需要针对相关视图模型更新相关实体,则只要您从上下文中检索实体时.Include().Map()调用就应该处理相关数据。