asp.net-identity - 有没有比通过 userManager.CreateAsync() 更快地批量创建身份用户(1M 行)的方法?
问题描述
我正在使用 ASP.NET Core 2.2 MVC 开发一个新站点,该站点正在替换经典 ASP 现有站点。他们有近 100 万用户需要转移到使用 Entity Framework 和 Identity Core 的新站点。我目前正在创建所有现有用户的列表,然后在 foreach 中调用 userManager.CreateAsync()。然后,我使用 userManager.AddToRoleAsync() 将该用户添加到角色(如果需要,我可以稍后在 SQL 中执行此操作)。每次通过 foreach 运行大约需要 3000-4000 毫秒,在每个异步调用之间平均分配,所以让它在一夜之间运行让我导入了 35K,这是不切实际的。
更新:好/坏消息是密码当前存储为纯文本,因此我可以将它们带入并对其进行哈希处理,准备好放入 AspNetUsers。显然,这是一种非常糟糕的密码存储方式,但会使当前的任务变得更简单!
我已经研究过编写一个存储过程来从代码中调用以将用户名、电子邮件和散列密码添加到 AspNetUsers 表中,但是该表需要所有字段,我不确定如何生成诸如 id 之类的项目(虽然我认为它只是一个 GUID,因此可以通过 NEWID()、SecurityStamp 和 ConcurrencyStamp 创建。
我通过创建所有现有身份用户的列表并将其添加到 Linq 查询中删除了重复项,因此只处理未处理的用户。此列表构建确实需要时间,但只需几分钟,所以这不是一个真正的问题。
我搜索了其他甚至稍微相关的帖子,但没有一个能完全回答这个具体问题。从那我至少发现了如何散列密码,这样如果我可以创建一种直接更新 AspNetUsers 表的新方法,而不是如何生成其他必要的字段 Id、SecurityStamp 和 ConcurrencyStamp。
即使我可以找出如何生成 SecurityStamp 和 ConcurrencyStamp,我也可以将数据传递到 Entity Framework 中的存储过程以添加到 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();
}
解决方案
是和不是。UserManager<TUser>.CreateAsync
主要用于三个目的:
它验证用户名和密码(如果提供)以确保它们符合您的身份配置中指定的要求。
它将用户名和电子邮件地址规范化在单独的列 (
NormalizedUserName
/NormalizedEmail
) 中,以便可以以标准化方式查找它们,同时仍保持索引。如果提供了密码,则密码将被加盐和散列。
前两个很容易在批量插入中复制。用户名要求非常简单,主要是确保它是 URL 安全的。至于标准化,据我所知,UserName
/Email
值只是全部大写。
但是,复制密码散列几乎是不可能的。但是,这也不是完全必要的。您可以选择简单地为每个用户强制重置密码,这不是一个坏主意,无论如何,当从一个身份验证系统迁移到另一个身份验证系统时,这几乎是不可避免的。除非您已经很糟糕并且存储了纯文本密码,否则无法知道用户的密码是什么以便迁移它们。
老实说,我什至不会费心尝试通过代码来做到这一点。这将是最慢和最困难的方法。只需使用 SSIS 包之类的东西,然后通过 SQL 将数据从字面上移动(根据需要进行修改)。
推荐阅读
- python - 只有当 post 和 get 请求被发送到包含 .delay() 和 AsyncResult() 的路由时才会处理 Celery 周期性任务
- delphi - 如何宣布 Bonjour 服务?
- http - HTTP POST 请求是否允许传递有效负载和输入参数
- java - 通过创建私钥的公钥并检查相等性来检查私钥和证书是否匹配是否正确?
- google-cloud-platform - 我需要联系谷歌云支持/未经授权的费用
- flutter - 列从左下角开始
- reactjs - 从 React 中的 useEffect 挂钩更改复选框选中状态?
- python - 如何从 python 脚本向命令行发送数据
- python - Pandas:如何比较两个 df 以创建一个新列,并比较两个 df 的新列和旧列?
- r - 如何将下面的数据转换为数据框以便我可以将它们绘制出来?(从 API 命令检索的数据)