asp.net-mvc - 实体框架添加记录两次
问题描述
这是我在堆栈上的第一篇文章,很抱歉给您带来不便或我的英语不好。我在实体框架向数据库表中添加两次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
在一个dbContext
实例或两个单独的dbContext
实例中,User
并Address
再次添加两次。
还尝试了数据库优先方法,但结果相同。
我假设它与表之间的关系有关,但不能指出正确的原因。
在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; }
所以User
和他们的地址Shop
共享Address
表。
另一种方法何时User
和Shop
设置为引用Address
表都可以正常工作。
但我想了解为什么这不起作用,最终使Address
能够级联User
或Shop
删除而不是使用 LINQ 删除Address
。
这是AddOrEdit
aUser
及其的操作方法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).
任何帮助表示赞赏。
谢谢 :)
解决方案
我建议将添加和更新拆分为单独的操作,因为通过 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()
一次。这确保了对各种相关实体所做的更改仅一起发生或根本不发生。
Update
可以通过使用和/w EntityState 之类的方法来更新实体,Attach
但是这种方法很容易出错,并且在 DbContext 可能已经在跟踪实体时可能会导致间歇性/情景运行Attach
时Update
错误具有相同 ID 的实例。如前所述,它还会导致 UPDATE SQL 语句的效率降低,并使您的系统容易受到意外篡改的影响。
推荐阅读
- ios - 用户如何在不直接点击选项卡的情况下更改选项卡?
- node.js - 告诉 Cloud Run 实例终止
- mysql - MYSQL 语法帮助
- css - Bootstrap 4 - 向上滚动时避免缩小导航栏
- python - 如何从 Python 中的 REST 响应中获取嵌套的 JSON 对象
- matlab - 尝试加载 Fieldtrip .mat Matlab 文件并发现 EEG 事件
- c++ - Gcc 错误优化 sse 函数
- scala - 如何在 Spark 的空 Dataframe 中添加行记录
- git - 如何从分叉的仓库中比较 github 中的本地分支?
- python - 如果在 Python 中找不到文件,如何使代码执行某些操作?