实体框架两次添加记录

时间:2021-03-27 20:39:04

标签: asp.net-mvc entity-framework

这是我在堆栈上的第一篇文章,对于给您带来的不便或我的英语不好,我们深表歉意。 我在实体框架 SaveChanges() 向数据库表中两次添加新的 User 记录时遇到问题。

我创建了一个 UserFormViewModel,用于根据来自模态窗口表单的用户输入发布数据。

这是我的UserFormViewModel

    public int Id { get; set; }

    [Required]
    [StringLength(50)]
    public string Username { get; set; }

    [Required]
    [StringLength(20)]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    [Required]
    [StringLength(50)]
    public string Name { get; set; }

    [Required]
    [StringLength(50)]
    public string Surname { get; set; }

    [Required]
    [StringLength(255)]
    [EmailAddress]
    public string Email { get; set; }

    public byte UserRoleId { get; set; }
    public Address Address { get; set; }

    public IEnumerable<UserRole> UserRoles { get; set; }

视图中的所有输入都正确映射到 AddOrEdit 操作方法参数,因此在通过 SaveChanges() 方法将数据保存到数据库时出现问题。它向表中添加了两次记录。

  • 我试过单独添加 User 并且它添加了 User 两次。
  • 然后我尝试单独添加 Address,它添加了 Address 两次。
  • 在一个 User 实例或两个分开的 Address 实例中保存 dbContextdbContext 时,UserAddress 再次添加两次.

也尝试过数据库优先的方法,但结果是一样的。

我假设它与表之间的关系有关,但无法指出正确的原因。

Address 模型类中有街道地址的标准属性:

 Id, StreetName, StreetNumber, Zip, City 

和两个外键:

    public int? UserId { get; set; }
    public int? ShopId { get; set; }

    public virtual User User { get; set; }
    public virtual Shop Shop { get; set; }

所以 UserShop 共享 Address 表作为他们的地址。

另一种方法是将 UserShop 设置为引用 Address 表,一切正常。

但我想了解为什么这不起作用,并最终使 Address 能够级联 UserShop 删除而不是使用 LINQ 删除 {{1 }}。

以下是 Address 及其 AddOrEdit 的操作方法 User

Address

这是仅添加 private readonly FurnitureStoreDbContext _context; public UsersController() { _context = new FurnitureStoreDbContext(); } protected override void Dispose(bool disposing) { _context.Dispose(); } [HttpPost] public ActionResult AddOrEdit(UserFormViewModel userForm) { if (userForm.Id == 0) { var newUser = new User { Username = userForm.Username, Password = userForm.Password, Name = userForm.Name, Surname = userForm.Surname, Email = userForm.Email, UserRoleId = userForm.UserRoleId }; _context.tblUsers.Add(newUser); _context.SaveChanges(); var newAddress = new Address { StreetName = userForm.Address.StreetName, StreetNumber = userForm.Address.StreetNumber, ZipCode = userForm.Address.ZipCode, City = userForm.Address.City, UserId = newUser.Id }; _context.tblStreetAddresses.Add(newAddress); _context.SaveChanges(); return Json(new { success = true, message = "Saved" }, JsonRequestBehavior.AllowGet); } else { var updatedUser = new User { Username = userForm.Username, Password = userForm.Password, Name = userForm.Name, Surname = userForm.Surname, Email = userForm.Email, UserRoleId = userForm.UserRoleId }; var updatedAddress = userForm.Address; _context.Entry(updatedUser).State = EntityState.Modified; _context.SaveChanges(); _context.Entry(updatedAddress).State = EntityState.Modified; _context.SaveChanges(); return Json(new { success = true, message = "Updated" }, JsonRequestBehavior.AllowGet); } } - 不添加 User 的调试日志。

Address 异常正在发生,因为我已将 Violation of UNIQUE KEY constraint 'UQ__Users__536C85E479927847' 字段设置为唯一且上下文尝试添加 Username 记录两次。

User

感谢任何帮助。

谢谢:)

2 个答案:

答案 0 :(得分:1)

我建议将添加和更新拆分为单独的操作,因为通过 Razor 等很容易在 UI 中配置适当的链接。

无论如何,您的问题与更新逻辑有关。使用 EF DbContexts 时,您应该在更新时寻求检索实体。例如:

var existingUser = _context.Users
    .Include(x => x.Address)
    .Single(x => x.UserId == userForm.Id);

从这里,从您的用户表单中复制您允许编辑的字段。例如,如果他们可以更改姓名、姓氏和电子邮件:

existingUser.Name = userForm.Name;
existingUser.Surname = userForm.Surname;
existingUser.Email = userForm.Email;

这种方法的好处是,EF 只会为实际更改的字段生成 UPDATE 语句,并且仅当至少有一个实际更改时才会生成。使用 Update() 方法或将实体的状态设置为 Modified 将导致 所有 列的 UPDATE 语句,即使实际上没有任何更改。

下一期将是参考文献。使用视图模型时,不应将实体传递给视图。像这样的代码通常有问题:

var updatedAddress = userForm.Address;
_context.Entry(updatedUser).State = EntityState.Modified;

这做出了很多危险的假设,即 userForm.Address 指向有效记录,并且 DbContext 尚未跟踪具有该 ID 的实体。

在前面从 DbContext 获取用户的示例语句中,我急切地加载了地址。这种方式不是在您的用户窗体视图模型中传递地址实体,而是传递地址详细信息的 POCO 视图模型,您可以根据需要使用它来编辑或插入地址:

if (existingUser.Address != null)
{
    existingUser.Address.AddressLine1 = userForm.Addres.AddressLine1;
    // ...
}
else
{
    var address = new Address
    {
        AddressLine1 = userForm.Addres.AddressLine1;
        // ...
    };
    existingUser.Address = address;
}

在处理多对多或多对一关系(例如将用户与商店关联时)时,获取引用非常重要。当关联到商店时,从用户的角度来看,Shop 实体应该已经存在,因此通过 ID 从 DbContext 获取它可以作为 ShopId 有效的验证,并且还避免了您最终无意中插入重复行的情况。

最后,只调用 SaveChanges() 一次。这可确保对各种相关实体所做的更改仅一起发生或根本不发生。

可以使用 UpdateAttach /w EntityState 之类的方法更新实体,但是这种方法很容易出错,并且在 DbContext 可能是当您在具有相同 ID 的新实例上使用 AttachUpdate 之类的内容时,已经在跟踪实体。如前所述,它还会导致 UPDATE SQL 语句效率降低,并使您的系统容易受到意外篡改。

答案 1 :(得分:0)

我只是想添加我的个人经验并进行修复。我有一个问题,我所有的电话都被发布了两次,但只在 Firefox 中发布。原来问题是firefox和iis通信的方式。我解决的方法是在 IIS 内的 https 绑定设置中禁用 HTTP/2。一天后,我才发现了解决方案,因为我在某处查看了空白的 href 或 src 代码。