【问题标题】:Entity Framework adding record twice实体框架添加记录两次
【发布时间】:2021-03-27 20:39:04
【问题描述】:

这是我在堆栈上的第一篇文章,很抱歉给您带来不便或我的英语不好。 我在 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
  • 在一个dbContext 实例或两个分开的dbContext 实例中保存UserAddress 时,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 删除 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 记录两次。

Step into: Stepping over property 'FurnitureStore.Models.User.get_Addresses'. To step into properties or operators, go to Tools->Options->Debugging and uncheck 'Step over properties and operators (Managed only)'.
Step into: Stepping over property 'FurnitureStore.Models.User.get_Addresses'. To step into properties or operators, go to Tools->Options->Debugging and uncheck 'Step over properties and operators (Managed only)'.
Opened connection at 3/27/2021 8:55:09 PM +01:00
Opened connection at 3/27/2021 8:55:09 PM +01:00
Started transaction at 3/27/2021 8:55:09 PM +01:00
Started transaction at 3/27/2021 8:55:09 PM +01:00
INSERT [dbo].[Users]([Username], [Password], [Name], [Surname], [Email], [UserRoleId])
VALUES (@0, @1, @2, @3, @4, @5)
SELECT [Id]
FROM [dbo].[Users]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()INSERT [dbo].[Users]([Username], [Password], [Name], [Surname], [Email], [UserRoleId])
VALUES (@0, @1, @2, @3, @4, @5)
SELECT [Id]
FROM [dbo].[Users]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()

-- @0: 'Username 1' (Type = String, Size = 50)
-- @0: 'Username 1' (Type = String, Size = 50)
-- @1: 'q1w2e3r4' (Type = String, Size = 20)
-- @2: 'Name 1' (Type = String, Size = 50)
-- @3: 'Surname 1' (Type = String, Size = 50)
-- @4: 'jjee@gmail.com' (Type = String, Size = 255)
-- @5: '2' (Type = Byte, Size = 1)
-- Executing at 3/27/2021 8:55:10 PM +01:00
-- @1: 'q1w2e3r4' (Type = String, Size = 20)
-- @2: 'Name 1' (Type = String, Size = 50)
-- @3: 'Surname 1' (Type = String, Size = 50)
-- @4: 'jjee@gmail.com' (Type = String, Size = 255)
-- Completed in 7 ms with result: SqlDataReader

-- @5: '2' (Type = Byte, Size = 1)
-- Executing at 3/27/2021 8:55:10 PM +01:00
Committed transaction at 3/27/2021 8:55:10 PM +01:00
-- Failed in 26 ms with error: Violation of UNIQUE KEY constraint 'UQ__Users__536C85E479927847'. Cannot insert duplicate key in object 'dbo.Users'. The duplicate key value is (Username 1).
The statement has been terminated.

Closed connection at 3/27/2021 8:55:10 PM +01:00
Closed connection at 3/27/2021 8:55:10 PM +01:00
Exception thrown: 'System.Data.Entity.Infrastructure.DbUpdateException' in EntityFramework.dll
An exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred in EntityFramework.dll but was not handled in user code
An error occurred while updating the entries. See the inner exception for details.

The thread 0x42cc has exited with code 0 (0x0).

感谢任何帮助。

谢谢:)

【问题讨论】:

  • 该代码看起来不错。您可能正在运行控制器两次,或者您的数据库中已经有该用户。而且您在“更新”分支上遇到了一个错误,因为您在尝试更新之前没有在实体上设置用户 ID。
  • updatedUser 没有设置Id 值(而数据库表显然有该字段)。这应该会导致更新问题,但不会重新插入您的编码方式。

标签: asp.net-mvc entity-framework


【解决方案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 将导致 all 列的 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 语句的效率降低,并使您的系统容易受到意外篡改。

【讨论】:

  • 抱歉,这是一个愚蠢的错误。我忘了在 onSubmit(form) 函数中输入 return false,所以表单被提交了两次。我一直专注于 C# 代码。无论如何,您的建议改进了我的代码并防止了未来的错误,所以非常感谢大家的时间和努力:)
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-20
    • 2016-09-17
    相关资源
    最近更新 更多