实体框架:更新用户失败
本文关键字:用户 失败 更新 框架 实体 | 更新日期: 2023-09-27 18:14:35
我正在尝试使用用户存储库服务更新用户。我仍在使用 IdentityUser
,只是将其修改为使用 int
作为主键而不是字符串/guid。
一切正常,直到我的Update
抛出异常。
所以这是我在BaseRepository
类中的更新方法:
public virtual void Update(T entity)
{
EntityEntry dbEntityEntry = _context.Entry<T>(entity);
dbEntityEntry.State = EntityState.Modified;
}
public virtual void Commit()
{
_context.SaveChanges();
}
调用本身是:
[HttpPost]
[Route("home/account/edit")]
public ActionResult Edit(User user)
{
if (!ModelState.IsValid)
return View(user);
string id = _userManager.GetUserId(User);
user.Id = id.To<int>();
_userService.Update(user);
_userService.Commit();
return RedirectToAction("Index");
}
(我认为没什么特别的(
所以现在,当Commit
保存我的更改时,它会抛出异常:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException:数据库操作预计影响 1 行,但实际上影响 0 行。自加载实体以来,数据可能已被修改或删除。有关了解和处理乐观并发异常的信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=527962。
我想,好吧,我检查了分析器,不可能那么棘手。探查器向我展示了实体框架发送给他的语句:
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [AspNetUsers] SET [AccessFailedCount] = @p0, [Birthday] = @p1, [ConcurrencyStamp] = @p2, [Email] = @p3, [EmailConfirmed] = @p4, [FirstName] = @p5, [Language] = @p6, [LastName] = @p7, [LockoutEnabled] = @p8, [LockoutEnd] = @p9, [MiddleName] = @p10, [NormalizedEmail] = @p11, [NormalizedUserName] = @p12, [PasswordHash] = @p13, [PhoneNumber] = @p14, [PhoneNumberConfirmed] = @p15, [SecurityStamp] = @p16, [TwoFactorEnabled] = @p17, [UserName] = @p18, [VerificationToken] = @p19
WHERE [Id] = @p20 AND [ConcurrencyStamp] = @p21;
SELECT @@ROWCOUNT;
',N'@p20 int,@p0 int,@p1 datetime2(7),@p2 nvarchar(4000),@p21 nvarchar(4000),@p3 nvarchar(256),@p4 bit,@p5 nvarchar(4000),@p6 nvarchar(4000),@p7 nvarchar(4000),@p8 bit,@p9 nvarchar(4000),@p10 nvarchar(4000),@p11 nvarchar(256),@p12 nvarchar(256),@p13 nvarchar(4000),@p14 nvarchar(4000),@p15 bit,@p16 nvarchar(4000),@p17 bit,@p18 nvarchar(256),@p19 nvarchar(4000)',@p20=1,@p0=0,@p1='1991-06-10 00:00:00',@p2=N'24fe11c5-a831-45ce-8960-16aa78b280df',@p21=N'24fe11c5-a831-45ce-8960-16aa78b280df',@p3=N'my@email.ch',@p4=0,@p5=N'Matthias',@p6=N'de-DE',@p7=N'Burger',@p8=0,@p9=NULL,@p10=NULL,@p11=NULL,@p12=NULL,@p13=NULL,@p14=NULL,@p15=0,@p16=NULL,@p17=0,@p18=NULL,@p19=NULL;
所以,[ConcurrencyStamp] = @p21
和@p21=N'24fe11c5-a831-45ce-8960-16aa78b280df'
检查了我对该用户的记录...
ConcurrencyStamp
22fa7d03-1382-4eee-8c17-878253fdf1c4
所以我的问题是:
- 那是什么
ConcurrencyStamp
? - 为什么实体框架对其进行筛选?他得到我的用户 ID。这还不够吗?
- 为什么用户对象比数据库中的对象
ConcurrencyStamp
不同?当实体框架想要筛选它时,它们不应该是相同的吗? - 有人有想法吗?我该如何/应该修复它?还是我做错了什么?
更新:
模型(现在查看模型也是(:
public class User : IdentityUser<int>, IEntity
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTime Birthday { get; set; }
public string Language { get; set; }
public ICollection<MailBox> MailBoxes { get; set; } = new List<MailBox>();
public string VerificationToken { get; set; }
}
> ConcurrencyStamp
是为了防止两个人对同一个用户进行两次不同的更改,而不知道彼此的更改。
/// <summary>
/// A random value that must change whenever a user is persisted to the store
/// </summary>
public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();
原则上,这应该在创建用户时分配一次,并且(我假设(应该有一种机制,例如数据库中的触发器,每当数据库中的条目更新时,它都会使值发生变化。
从这个答案:
如果你在 OnModelCreateating(( 方法中查看 IdentityDbContext 的实现,你会发现:
builder.Entity<TUser>(b => { .... b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken(); ....
因此,实体框架知道这应该被视为并发令牌。它检查属性的值以及 ID,以确保在用户对象从数据库加载出来后更改用户时更新失败。
至于为什么值不一样,这是你可能必须通过调试弄清楚的事情。以下是您可以检查的一些理论:
- 也许用户对象在加载后确实发生了变化?如果用户对象使用其原始值加载两次,然后独立更改两次,则会发生这种情况。
- 也许您正在根据传入 Web 请求的值构造用户对象,但您没有根据从数据库中提取 ConcurrencyStamp 令牌时存在的值映射该令牌,因此它保留了首次构造时获得的随机生成的 GUID?