【问题标题】:Entity Framework : update user fails实体框架:更新用户失败
【发布时间】:2016-10-23 20:51:41
【问题描述】:

我正在尝试使用用户存储库服务更新用户。我仍在使用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

我想,好吧,我检查了分析器,不可能那么棘手。 Profiler 向我展示了实体框架发送给他的语句:

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'

检查了我对该用户的记录...

ConcurrencyStamp22fa7d03-1382-4eee-8c17-878253fdf1c4

所以我的问题是:

  • ConcurrencyStamp 是什么?
  • 为什么 Entity Framework 对其进行过滤?他得到了我的用户 ID。这还不够吗?
  • 为什么用户对象有另一个ConcurrencyStamp 而不是数据库中的那个? Entity Framework要过滤的时候,不应该是一样的吗?
  • 有人有想法吗?我怎么能/应该修复它?还是我做错了什么?

更新

模型(现在也是视图模型):

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; }
}

【问题讨论】:

  • 你能给我们看看你的模型\viewmodel吗
  • @SimonPrice 已更新 :) 模型现在也是视图模型。现在或多或少是为了测试。
  • 你的代码很奇怪。你用的是登录用户的ID,但是是通过用户的数据?此代码尝试始终创建具有登录用户 ID 的用户。此外,您似乎永远不会附加模型。您是否在更新之前尝试附加模型?
  • 是的,这是为了更新自己的个人资料数据。所以传递的用户是登录用户。
  • 您是否将并发令牌 (ConcurrencyStamp) 传递回操作?它用于检查您尝试更新的模型是否尚未通过其他操作进行更新。如果token不同,更新失败(因为并发token会成为where子句的一部分)

标签: c# entity-framework asp.net-core


【解决方案1】:

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();

原则上,这应该在创建用户时分配一次,并且(我假设)应该有一种机制,如数据库中的触发器,只要数据库中的条目更新,就会导致值发生变化。

来自this answer

如果你看一下 OnModelCreating() 方法中 IdentityDbContext 的实现,你会发现:

builder.Entity<TUser>(b =>
{
....
    b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken();
....

所以实体框架知道这应该被视为并发令牌。它会检查属性的值以及 ID,以确保如果用户在从数据库中加载出来后发生更改,则更新失败。

至于为什么值不一样,这可能是你必须通过调试才能弄清楚的。以下是您可能会检查的一些理论:

  1. 也许用户对象在加载后真的发生了变化?如果用户对象使用其原始值加载了两次,然后独立更改了两次,那么就会发生这种情况。
  2. 也许您正在根据传递给 Web 请求的值构建用户对象,但您没有根据从数据库中提取它时的值映射 ConcurrencyStamp 令牌,因此它保持随机 -生成它在第一次构建时获得的 GUID?

【讨论】:

  • 几乎是第二个。我的视图发送了一个带有更新数据的新用户对象,我试图将其更新到数据库。这里没有设置ConcurrencyStamp(从原始对象映射,因为视图正在重置模型),因此生成了一个新的 ConcurrencyStamp 并且肯定它们不匹配。
猜你喜欢
  • 2020-07-19
  • 1970-01-01
  • 1970-01-01
  • 2011-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-22
相关资源
最近更新 更多