EF Core with Cosmos DB provider, UserManager AddLoginAsync gives ConcurrencyFailure

岁酱吖の 提交于 2021-02-11 12:49:46

问题


Creating a new user, fetching the user again with usermanager for testing, and then using the method AddLoginAsync with the recently fetched user gives the error

ConcurrencyFailure, Optimistic concurrency failure, object has been modified.

When fetching the user the "ConcurrencyStamp" has the correct etag, but after the "AddLoginAsync" I can see the user object has an invalid etag, the ConcurrencyStamp is a GUID.

I have followed the documentation and added this to the IdentityUser model configuration:

//builder: EntityTypeBuilder<ApplicationUser>
builder.Property(d => d.ConcurrencyStamp)
       .IsETagConcurrency();

Startup:

services.AddDbContextPool<ApplicationDbContext>(option =>
{
    var connectionConfig = new DbConnectionString("DefaultConnection", Configuration);
    option.UseCosmos(connectionConfig.ConnectionString, connectionConfig.Database);
});

services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

In the Cosmos DB Emulator I can see two documents created:

{
    "Id": "b66b520e-60d2-4ba3-8347-6c168be9a1e5",
    "AccessFailedCount": 0,
    "_etag": "\"00000000-0000-0000-d54b-cffa72ea01d6\"",
    "Created": "2020-12-18T14:41:03.962769Z",
    "Discriminator": "ApplicationUser",
    "Email": "@email.com",
    "EmailConfirmed": false,
    "Encounters": 0,
    "LockoutEnabled": true,
    "LockoutEnd": null,
    "Nickname": null,
    "NormalizedEmail": "@EMAIL.COM",
    "NormalizedUserName": "@EMAIL.COM",
    "OrderNum": 0,
    "PasswordHash": null,
    "PhoneNumber": null,
    "PhoneNumberConfirmed": false,
    "SecurityStamp": "PPFUV4JXJ72KBYNXBPPV3FVJMIG",
    "TwoFactorEnabled": false,
    "UserName": "@email.com",
    "id": "ApplicationUser|b66b520e-60d2-4ba3-8347-6c168be9a1e5",
    "_rid": "VA5JAKKvHtQNAAAAAAAAAA==",
    "_self": "dbs/VA5JAA==/colls/VA5JAKKvHtQ=/docs/VA5JAKKvHtQNAAAAAAAAAA==/",
    "_attachments": "attachments/",
    "_ts": 1608302464 }

{
    "LoginProvider": "Microsoft",
    "ProviderKey": "***",
    "Discriminator": "IdentityUserLogin<string>",
    "ProviderDisplayName": "Microsoft",
    "UserId": "b66b520e-60d2-4ba3-8347-6c168be9a1e5",
    "id": "IdentityUserLogin<string>|Microsoft|***",
    "_rid": "VA5JAKKvHtQOAAAAAAAAAA==",
    "_self": "dbs/VA5JAA==/colls/VA5JAKKvHtQ=/docs/VA5JAKKvHtQOAAAAAAAAAA==/",
    "_etag": "\"00000000-0000-0000-d54b-d4ad146f01d6\"",
    "_attachments": "attachments/",
    "_ts": 1608302472
}

How to fix the issue?


回答1:


I could fix the issue by creating a new UserStore,

   public class ApplicationUserStore 
        : UserStore<ApplicationUser,ApplicationRole,ApplicationDbContext>
    {
        public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null)
            : base(context, describer)
        {
        }

        public override async Task<IdentityResult> UpdateAsync(ApplicationUser user, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            Context.Attach(user);
            Context.Update(user);
            try
            {
                await SaveChanges(cancellationToken);
            }
            catch (DbUpdateConcurrencyException)
            {
                return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
            }

            return IdentityResult.Success;
        }
    }

But I am now facing another issue when using signinmanager:

InvalidOperationException: The LINQ expression 'DbSet<IdentityUserRole>() .Join( inner: DbSet(), outerKeySelector: i => i.RoleId, innerKeySelector: a => a.Id, resultSelector: (i, a) => new TransparentIdentifier<IdentityUserRole, ApplicationRole>( Outer = i, Inner = a ))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

I found the solution by overriding another method in the UserStore:

public override async Task<IList<string>> GetRolesAsync(ApplicationUser user, CancellationToken cancellationToken = default)
{
    string userId = user.Id;
    var roleIds = await Context.UserRoles.Where(ur => ur.UserId == userId)
        .Select(ur => ur.RoleId)
        .ToArrayAsync();

    return await Context.Roles.Where(r => roleIds.Contains(r.Id))
        .Select(r => r.Name)
        .ToListAsync();
}


来源:https://stackoverflow.com/questions/66053434/optimistic-concurrency-failure-object-has-been-modified-using-cosmos-with-ef

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!