这是我在堆栈上的第一篇文章,对于给您带来的不便或我的英语不好,我们深表歉意。
我在实体框架 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 删除 {{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
感谢任何帮助。
谢谢:)
答案 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()
一次。这可确保对各种相关实体所做的更改仅一起发生或根本不发生。
可以使用 Update
和 Attach
/w EntityState 之类的方法更新实体,但是这种方法很容易出错,并且在 DbContext 可能是当您在具有相同 ID 的新实例上使用 Attach
或 Update
之类的内容时,已经在跟踪实体。如前所述,它还会导致 UPDATE SQL 语句效率降低,并使您的系统容易受到意外篡改。
答案 1 :(得分:0)
我只是想添加我的个人经验并进行修复。我有一个问题,我所有的电话都被发布了两次,但只在 Firefox 中发布。原来问题是firefox和iis通信的方式。我解决的方法是在 IIS 内的 https 绑定设置中禁用 HTTP/2。一天后,我才发现了解决方案,因为我在某处查看了空白的 href 或 src 代码。