简体   繁体   English

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

[英]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. 我也使用延迟加载,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; }
}

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. Automapper非常适合将实体映射到视图模型,但我会避免使用它从视图模型映射到实体。 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. 这意味着您必须将100%的实体域模型发送到客户端,从而更多地了解您的域结构,然后接受可能包含客户端应用程序无意做出的更改的扩展域模型。 (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. 在您提供更新实体的操作的其他情况下,回发的模型应仅包含要更新的实体的ID以及允许客户端更新的特定值。 (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. 为了避免您遇到的问题以及安全/陈旧问题,您应该从Update post调用的上下文加载实体,验证当前用户的授权,检查行版本#或时间戳以确保记录不是' t stale,验证您更新的详细信息,然后,一旦您完全确定视图模型中的数据不会对您的实体造成任何风险,您可以使用automapper的.Map(source, detination)来复制值。 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. 如果您需要针对相关视图模型更新相关实体,那么只要您从上下文中检索实体时.Include()这些相关实体,那么.Map()调用应该处理相关数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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