【发布时间】:2021-02-06 06:47:54
【问题描述】:
上下文
我有一个项目,我正在使用实体框架核心。该项目将拥有可以注册的用户,当然,之后可以登录。他们还可以AFTERWARDS编辑他们的个人资料,这就是问题所在。当尝试编辑用户设置(数据,他在注册时不输入,登录)时,他还可以设置一个地址,这是用户的依赖实体。当尝试添加地址时,它也失败了
乐观并发失败,对象已被修改
流程
首先,用户只需提供电子邮件地址和密码即可注册。一封电子邮件被发送给他以确认他的帐户。当他这样做时,他能够登录。登录时,他检索一个 JWT,他可以使用它来执行他的请求。目前唯一的其他控制器方法是编辑他的个人资料设置,他可以在其中添加数据,例如
- 名字(字符串)
- 姓氏(字符串)
- 电话号码(字符串)
- 地址(地址)
- 街道
- 户型
- 城市
- 邮政编码
问题及相关代码
在详细介绍之前,我想向您展示我是如何处理这种关系的
用户.cs
public sealed class User : IdentityUser<Guid>
{
// Other fields...
[ProtectedPersonalData]
public Address Address { get; set; }
}
地址.cs
public sealed class Address
{
// Other fields...
public Guid AddressId { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
public Address()
{
AddressId = Guid.NewGuid();
}
}
DbContext.cs
public sealed class DbContext : IdentityDbContext<User, Role, Guid>
{
public DbSet<User> Users { get; set; }
public DbSet<Address> Addresses { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql(
"server=localhost;database=db;user=root;password=example"
);
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<User>()
.HasOne(u => u.Address)
.WithOne(a => a.User)
.HasForeignKey<Address>(a => a.UserId);
base.OnModelCreating(builder);
}
}
当我现在发出 PUT 请求以编辑配置文件时,我正在发送此数据
{
"firstname": "Austin",
"lastname": "Powers",
"Address": {
"Street": "Mr. Evil Way",
"HouseNumber": "20",
"ZipCode": "10115"
},
"PhoneNumber": "+492148 48484"
}
我的控制器,看起来像这样
public async Task<ActionResult<User>> EditProfile(EditProfileResource editProfileResource)
{
User user = await _userManager.GetUserAsync(User);
// _mapper is an automapper instance
user = _mapper.Map(editProfileResource, user);
user.ProfileSetup = true;
IdentityResult result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
return NoContent();
}
return Problem(result.Errors.First().Description, null, StatusCodes.Status400BadRequest);
}
那么接下来会发生什么。
- 控制器自动将请求正文转换为
EditProfileResource,它看起来像这样并有一个地址资源
public sealed class EditProfileResource
{
public string FirstName { get; set; }
public string LastName { get; set; }
public AddressResource Address { get; set; }
public string PhoneNumber { get; set; }
}
// Separate file of course
public sealed class AddressResource
{
public string Street { get; set; }
public string HouseNumber { get; set; }
public string ZipCode { get; set; }
public string City { get; set; }
}
所以我的editProfileResource 拥有所有正确的数据集!问题?当我现在将用户保存在内容为IdentityResult result = await _userManager.UpdateAsync(user); 的行中时,我收到了错误Optimistic concurrency failure, object has been modified。当我从EditProfileResource 中删除AddressResource 时,它可以正常工作,但我也想编辑地址。
但是为了正确理解,我将展示代码是如何实现的以及其中的内容,这可能会导致错误,但我不知道修复会是什么
User user = await _userManager.GetUserAsync(User);
-> 在这里,我们有用户对象,存储在数据库中,没有名字,没有姓氏,没有地址
user = _mapper.Map(editProfileResource, user);
-> 这现在改变了用户对象,所以它现在有一个名字、姓氏,甚至一个地址对象。地址对象还具有具有正确 guid 和用户对象的用户对象。所以一切都是正确的。
IdentityResult result = await _userManager.UpdateAsync(user);
-> 这样会报错,可能是因为User中的Address也有User对象吧?
经过测试的解决方案
所以我接下来要做的是尝试,改变储蓄机制。我没有做IdentityResult result = await _userManager.UpdateAsync(user);,而是做了
await using (DbContext context = new DbContext())
{
context.Addresses.Add(user.Address);
await context.SaveChangesAsync();
}
但是,由于外键约束,这失败了?
无法添加或更新子行:外键约束失败(
db.Addresses, CONSTRAINTFK_Addresses_AspNetUsers_UserIdFOREIGN KEY (UserId) REFERENCESAspNetUsers(Id) ON DELETE CASCADE)
所以我在没有context.Addresses.Add(user.Address);的情况下尝试了它
这可行,但由于我没有将它添加到上下文中,因此数据库中没有地址。我究竟做错了什么? :/
什么是正确的方法,同时编辑两个实体并将它们都保存到数据库中?
更新
更新 1
我尝试将其拆分为两种方法,以确保它与尝试一次更新多个数据集无关
[HttpPut("userdata")]
public async Task<ActionResult<User>> EditProfile(EditProfileResource editProfileResource)
{
User user = await _userManager.GetUserAsync(User);
_mapper.Map(editProfileResource, user);
user.ProfileSetup = user.ProfileSetup ? user.ProfileSetup : user.Address != null;
IdentityResult result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
return NoContent();
}
return Problem(result.Errors.First().Description, null, StatusCodes.Status400BadRequest);
}
[HttpPut("address")]
public async Task<ActionResult> EditAddress(AddressResource addressResource)
{
User user = await _userManager.GetUserAsync(User);
Address address = _mapper.Map<AddressResource, Address>(addressResource);
address.User = user;
address.UserId = user.Id;
await using (DbContext context = new DbContext())
{
context.Addresses.Add(address);
await context.SaveChangesAsync();
}
return NoContent();
}
再一次,编辑用户是可行的,但是当我想保存地址时,由于已经存在密钥,它会失败。怎么可能?
【问题讨论】:
-
我会说这是 db 类设计中的一个缺陷。该地址不应与其他类别的用户分开。如何为多个用户重复使用一个地址?
-
不止一个人可以住在同一个地址。地址应该在另一个表中。
-
补充一下 Mich 所说的:即使我们不考虑地址,每个关系都会发生此错误。地址只是其中之一。例如,如果我有一个照片,这是一个不同的类,因为一个用户可以有一张照片,另一个实体可以有一张照片,而且我不希望每个表都有 3 列照片,那么同样的问题会发生
-
您能否确认您是否配置了表用户或地址的任何触发器?
-
@UmairZafar 我可以确认我没有使用任何触发器
标签: c# asp.net .net asp.net-core entity-framework-core