有没有比通过userManager.CreateAsync()更快的方法来批量创建身份用户(1M行)?



我正在 ASP.NET Core 2.2 MVC中开发一个新站点,该站点正在替换经典ASP现有站点。 他们有近一百万用户需要转移到使用实体框架和标识核心的新网站。 我目前正在创建所有现有用户的列表,然后在每个for中调用userManager.CreateAsync()。 然后,我使用该userManager.AddToRoleAsync()将该用户添加到角色中(如有必要,我可以稍后在SQL中执行此操作)。 每次通过foreach运行大约需要3000-4000毫秒,在每个异步调用之间平均分配,因此让它在一夜之间运行让我导入了35K,这是不切实际的。

更新:好消息/坏消息是密码目前存储为纯文本,因此我可以将它们引入并对其进行哈希处理,准备放入AspNetUsers中。 显然,这是一种非常糟糕的存储密码的方式,但使当前任务更简单一些!

我已经考虑编写一个存储过程来调用代码以将用户名,电子邮件和哈希密码添加到AspNetUsers表中,但是该表需要所有字段,并且我不确定如何生成诸如id之类的项(尽管我认为它只是一个GUID,因此可以通过NEWID()创建), SecurityStamp 和 ConcurrencyStamp。

我通过创建所有现有标识用户的列表并将其添加到 Linq 查询中来删除重复项,以便仅处理未处理的用户。 这个列表构建确实需要时间,但只需要几分钟,所以这不是一个真正的问题。

我已经搜索了其他帖子,甚至略有相关,但没有一个完全回答这个具体问题。 由此,我至少发现了如何散列密码,以便我可以创建一种直接更新 AspNetUsers 表的新方法,但不能创建如何生成其他必要字段 Id、SecurityStamp 和 ConcurrencyStamp。

即使我只是了解如何生成 SecurityStamp 和 ConcurrencyStamp,我也可以将数据传递到实体框架中的存储过程以添加到 AspNetUsers 表中。(我们在实体框架中使用存储过程 - 我知道这不是"完成的事情",但现有的代码库是我无法取消选择的存储过程的挂毯!

public async Task<ViewResult> LoadExisting()
{
List<ImportModel> existing = new List<ImportModel>();
var users = userManager.Users.ToList();
existing = _context.Individual
.Where(i => (i.AccessId == 20 && !users.Any(s => s.Email == i.Email) ))
.Select(i => new ImportModel { Email = i.Email, Password = i.Password, existingId = i.existingId })
.ToList();
List<string> successes = new List<string>();
List<string> duplicates = new List<string>();
List<string> failures = new List<string>();
IdentityResult result = new IdentityResult();
IdentityResult roleresult = new IdentityResult();
foreach (ImportModel indiv in existing)
{
var hasher = userManager.PasswordHasher;
AppUser user = new AppUser
{
UserName = indiv.Email,
Email = indiv.Email,
Individual_id = indiv.IndividualId
};
//indiv.Password = hasher.HashPassword(user, indiv.Password); // this works
// Do something faster here
result = await userManager.CreateAsync(user, indiv.Password);
if (result.Succeeded)
{
// Add to relevant role, set above
roleresult = await userManager.AddToRoleAsync(user, "ARole");
successes.Add(indiv.Email + ",");
}
else
{
foreach (var error in result.Errors)
{
if (error.Code == "DuplicateUserName")
{
duplicates.Add(indiv.Email + ",");
}
else
{
failures.Add(indiv.Email + ", <strong>" + error.Code + "</strong>");
}
}                    
}
}
ViewData["LoadExistingSuccesses"] = successes;
ViewData["LoadExistingDuplicates"] = duplicates;
ViewData["LoadExistingFailures"] = failures;
return View();
}

用户管理器/登录管理器和后端 EF CoreIdentityDbContext之间有一个抽象层。此抽象已经有一个标志来防止立即保存更改。由于泛型参数的数量,访问此标志很复杂。但这是可以做到的。如果此标志可以某种方式直接配置,这将有所帮助。

标识框架的开发人员未在运行时设置此标志。您可能会遇到实现错误。此解决方法取决于标识框架的内部。它可能会在版本之间中断。

public interface IIdentityAutoSave
{
bool AutoSave { get; set; }
}
public static class IdentityExtension
{
// Why is this so hard??
public static IdentityBuilder AddSaveHelper(this IdentityBuilder builder)
{
var service = builder.Services
.Where(s => s.ServiceType.IsGenericType && s.ServiceType.GetGenericTypeDefinition() == typeof(IUserStore<>))
.Single();
var type = service.ImplementationType;
while(type != null)
{
if (type.IsGenericType) {
var genericType = type.GetGenericTypeDefinition();
if (genericType == typeof(UserOnlyStore<,,,,,>))
{
// Implement IIdentityAutoSave with IdentitySaveUser using the same generic parameters
builder.Services.AddScoped(
typeof(IIdentityAutoSave),
typeof(IdentitySaveUser<,,,,,>).MakeGenericType(type.GenericTypeArguments)
);
return builder;
}
else if (genericType == typeof(UserStore<,,,,,,,,>))
{
// Implement IIdentityAutoSave with IdentitySaveUserRole using the same generic parameters
builder.Services.AddScoped(
typeof(IIdentityAutoSave),
typeof(IdentitySaveUserRole<,,,,,,,,>).MakeGenericType(type.GenericTypeArguments)
);
return builder;
}
}
type = type.BaseType;
}
throw new NotSupportedException();
}
}
public class IdentitySaveUser<TUser, TContext, TKey, TUserClaim, TUserLogin, TUserToken> : IIdentityAutoSave
where TUser : IdentityUser<TKey>
where TContext : DbContext
where TKey : IEquatable<TKey>
where TUserClaim : IdentityUserClaim<TKey>, new()
where TUserLogin : IdentityUserLogin<TKey>, new()
where TUserToken : IdentityUserToken<TKey>, new()
{
private readonly UserOnlyStore<TUser, TContext, TKey, TUserClaim, TUserLogin, TUserToken> store;
public IdentitySaveUser(IUserStore<TUser> store)
{
this.store = store as UserOnlyStore<TUser, TContext, TKey, TUserClaim, TUserLogin, TUserToken>;
if (this.store == null)
throw new NotSupportedException();
}
public bool AutoSave { get { return store.AutoSaveChanges; } set { store.AutoSaveChanges = value; } }
}
public class IdentitySaveUserRole<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim> : IIdentityAutoSave
where TUser : IdentityUser<TKey>
where TRole : IdentityRole<TKey>
where TContext : DbContext
where TKey : IEquatable<TKey>
where TUserClaim : IdentityUserClaim<TKey>, new()
where TUserRole : IdentityUserRole<TKey>, new()
where TUserLogin : IdentityUserLogin<TKey>, new()
where TUserToken : IdentityUserToken<TKey>, new()
where TRoleClaim : IdentityRoleClaim<TKey>, new()
{
private readonly UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim> store;
private readonly RoleStore<TRole, TContext, TKey, TUserRole, TRoleClaim> roleStore;
public IdentitySaveUserRole(IUserStore<TUser> store, IRoleStore<TRole> roleStore)
{
this.store = store as UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim>;
this.roleStore = roleStore as RoleStore<TRole, TContext, TKey, TUserRole, TRoleClaim>;
if (this.store == null || roleStore == null)
throw new NotSupportedException();
}
public bool AutoSave { get { return store.AutoSaveChanges; } set { store.AutoSaveChanges = roleStore.AutoSaveChanges = value; } }
}

用法;

public void ConfigureServices(IServiceCollection services)
...
services.AddIdentity...(o => ...)
.AddEntityFrameworkStores<Context>()
.AddSaveHelper();
... ControllerAction([FromServices] Context db, [FromServices] UserManager<User> manager, [FromServices] IIdentityAutoSave save){
// prevent usermanager from saving
save.AutoSave = false;
foreach(...) {
await manager.CreateAsync(...);
}
// save all changes at once
await db.SaveChangesAsync();
}

是和否。UserManager<TUser>.CreateAsync主要有三个目的:

  1. 它会验证用户名和密码(如果提供),以确保它们符合标识配置中指定的要求。

  2. 它将用户名和电子邮件地址规范化在单独的列(NormalizedUserName/NormalizedEmail),以便可以在保持索引的同时以标准化的方式查找它们。

  3. 如果提供了密码,密码将被加盐和哈希处理。

前两个很容易在批量插入中复制。用户名要求非常简单,主要只是确保它是URL安全的。至于规范化,据我所知,UserName/Email值只是全部大写。

但是,复制密码散列几乎是不可能的。但是,这也并非完全必要。您可以选择简单地为每个用户强制重置密码,无论如何,这不是一个坏主意,无论如何,当从一个身份验证系统迁移到另一个身份验证系统时,这几乎是不可避免的。除非您已经很糟糕并存储了纯文本密码,否则无法知道用户的密码是什么以迁移它们。

老实说,我什至懒得尝试通过代码执行此操作。这将是最慢和最困难的方法。只需使用 SSIS 包之类的东西,然后通过 SQL 移动数据(根据需要进行修改)。

相关内容

  • 没有找到相关文章

最新更新