首页 > 解决方案 > 使用通用存储库模式和实体框架更新两个相关对象

问题描述

我正在使用通用存储库和实体框架。我可以正常更新其中一个类,但我无法更新它们之间的关系。

我还使用延迟加载、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);
    }

标签: c#asp.net-mvcentity-frameworkrepository

解决方案


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

提交操作应编码为:

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

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

通过传递实体,或直接映射到实体的视图模型,用于更新实体某些方面的方法,我可以:

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

不要相信从客户那里收到的任何东西。

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

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


推荐阅读