Matthias Burger Matthias Burger - 1 month ago 36
C# Question

Entity Framework : update user fails

I'm trying to update a user with a user repository service. I'm still using the

IdentityUser
, just modified it to use
int
as primary key instead of string/guid.

Everything works fine, until my
Update
throws an exception.

So this is my update method in my
BaseRepository
class:

public virtual void Update(T entity)
{
EntityEntry dbEntityEntry = _context.Entry<T>(entity);
dbEntityEntry.State = EntityState.Modified;
}

public virtual void Commit()
{
_context.SaveChanges();
}


The call itself goes:

[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");
}


(nothing special I think)

So now, when it's in
Commit
to save my changes, it throws an exception:


Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.


I thought, okay, I check the profiler, couldn't be that tricky.
Profiler shows me the statement, entity framework sent him:

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'matthias-burger@gmx.de',@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;


So,
[ConcurrencyStamp] = @p21
and
@p21=N'24fe11c5-a831-45ce-8960-16aa78b280df'


Checked my record of that user...

ConcurrencyStamp
is
22fa7d03-1382-4eee-8c17-878253fdf1c4


So my questions are:


  • What the hell is that
    ConcurrencyStamp
    ?

  • Why the hell is Entity Framework filtering for it? He gets my User ID. Isn't that enough?

  • Why the hell has the user object another
    ConcurrencyStamp
    than the one in the database? When Entity Framework wants to filter for it, shouldn't they be the same?

  • Does anybody have an idea? How could/should I fix it? Or am I doing something wrong?



Update:

Model (for now viewmodel, too):

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

Answer

The ConcurrencyStamp is there to prevent two people from making two different changes to the same user without being aware of one another's changes.

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

In principle, this should be assigned once when creating a user, and (I'm assuming) there should be a mechanism like a trigger in the database that causes the value to change whenever the entry in the database gets updated.

From this answer:

If you look at the implementation of IdentityDbContext in the OnModelCreating() method, you'll find:

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

So Entity Framework is aware that this is supposed to be treated as a concurrency token. It checks the property's value along with the ID to ensure that the update fails if the user has been changed since that user object was loaded out of the database.

As to why the value isn't the same, that's something you'll probably have to figure out through debugging. Here are a few theories you might check:

  1. Maybe the user object really was changed since it got loaded? If the user object gets loaded twice with its original values and then changed twice independently, then this would happen.
  2. Maybe you're constructing the user object based on values passed into a web request, but you're not mapping the ConcurrencyStamp token based on the value that was there when you pulled it from the database, so it's keeping the randomly-generated GUID that it gets when it's first constructed?