EF Core 与 Cosmos DB 提供程序,UserManager AddLoginAsync 提供 ConcurrencyFailure
EF Core with Cosmos DB provider, UserManager AddLoginAsync gives ConcurrencyFailure
创建一个新用户,用 usermanager 再次获取用户进行测试,然后对最近获取的用户使用方法 AddLoginAsync 给出错误
ConcurrencyFailure, Optimistic concurrency failure, object has been modified.
获取用户时,“ConcurrencyStamp”具有正确的 etag,但在“AddLoginAsync”之后我可以看到用户对象具有无效的 etag,ConcurrencyStamp 是一个 GUID。
我已按照文档将其添加到 IdentityUser 模型配置中:
//builder: EntityTypeBuilder<ApplicationUser>
builder.Property(d => d.ConcurrencyStamp)
.IsETagConcurrency();
启动:
services.AddDbContextPool<ApplicationDbContext>(option =>
{
var connectionConfig = new DbConnectionString("DefaultConnection", Configuration);
option.UseCosmos(connectionConfig.ConnectionString, connectionConfig.Database);
});
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
在 Cosmos DB 模拟器中,我可以看到创建了两个文档:
{
"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
}
如何解决这个问题?
我可以通过创建一个新的 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;
}
}
但是我现在在使用 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.
我通过覆盖 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();
}
创建一个新用户,用 usermanager 再次获取用户进行测试,然后对最近获取的用户使用方法 AddLoginAsync 给出错误
ConcurrencyFailure, Optimistic concurrency failure, object has been modified.
获取用户时,“ConcurrencyStamp”具有正确的 etag,但在“AddLoginAsync”之后我可以看到用户对象具有无效的 etag,ConcurrencyStamp 是一个 GUID。
我已按照文档将其添加到 IdentityUser 模型配置中:
//builder: EntityTypeBuilder<ApplicationUser>
builder.Property(d => d.ConcurrencyStamp)
.IsETagConcurrency();
启动:
services.AddDbContextPool<ApplicationDbContext>(option =>
{
var connectionConfig = new DbConnectionString("DefaultConnection", Configuration);
option.UseCosmos(connectionConfig.ConnectionString, connectionConfig.Database);
});
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
在 Cosmos DB 模拟器中,我可以看到创建了两个文档:
{
"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
}
如何解决这个问题?
我可以通过创建一个新的 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;
}
}
但是我现在在使用 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.
我通过覆盖 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();
}