asp.net-core - 由于异常 'A second operation started on this context before a previous',无法以静态方法在 ASP.NET Core 中播种数据

标签 asp.net-core entity-framework-core

我正在尝试使用以下代码为我的数据库做种:

Startup.Configure :

app.UseCors("AllowAll")
   .UseMiddleware<JwtBearerMiddleware>()
   .UseAuthentication()
   .SeedDatabase() <= here
   .UseHttpsRedirection()
   .UseDefaultFiles()
   .UseMvc()
   .UseSpa(SpaApplicationBuilderExtensions => { });

SeedDatabase方法:

public static IApplicationBuilder SeedDatabase(this IApplicationBuilder app)
{
            IServiceProvider serviceProvider = app.ApplicationServices.CreateScope().ServiceProvider;
            try
            {
                UserManager<ApplicationUser> userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();
                RoleManager<IdentityRole> roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
                IConfiguration configuration = serviceProvider.GetService<IConfiguration>();
                ThePLeagueContext dbContext = serviceProvider.GetService<ThePLeagueContext>();
                DataBaseInitializer.SeedUsers(userManager, roleManager, configuration, dbContext);
                DataBaseInitializer.SeedTeams(dbContext);

            }
            catch (Exception ex)
            {
                ILogger<Program> logger = serviceProvider.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred while seeding the database.");
            }

            return app;
  }

一切正常,直到我添加 ThePLeagueContext dbContext = serviceProvider.GetService<ThePLeagueContext>();然后是 DataBaseInitializer.SeedTeams(dbContext)

DataBaseInitializer.SeedTeams(dbContext) :

public static async void SeedTeams(ThePLeagueContext dbContext)
{
        List<Team> teams = new List<Team>();

        // 7 because we have 7 leagues
        for (int i = 0; i < 7; i++)...

        if (dbContext.Teams.Count() < teams.Count)
        {
            foreach (Team newTeam in teams)
            {                    
                await dbContext.Teams.AddAsync(newTeam);
                await dbContext.SaveChangesAsync();
            }
        }
}

当我尝试使用上述代码对数据库进行播种时,出现以下异常:

System.InvalidOperationException: 'A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations.'

我的数据库上下文注册了生命周期Scoped .

我发现了两个解决方法:

  1. 当我将数据库上下文更改为 Transient 时播种问题消失了。然而,这会导致应用程序出现其他问题,因此我无法使用 Transient
  2. 当我调用 DatabaseInitializer.SeedTeams(dbContext)DatabaseInitializer.SeedUsers(...) 里面方法,这也有效,我不知道为什么。

DatabaseInitializer.SeedUsers(...)方法:

public async static void SeedUsers(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IConfiguration configuration, ThePLeagueContext dbContext)
{
            string[] roles = new string[] { AdminRole, SuperUserRole, UserRole };

            foreach (string role in roles)
            {
                if (!roleManager.Roles.Any(r => r.Name == role))
                {
                    IdentityRole newRole = new IdentityRole
                    {
                        Name = role,
                        NormalizedName = role.ToUpper()
                    };
                    await roleManager.CreateAsync(newRole);

                    if (role == AdminRole)
                    {
                        await roleManager.AddClaimAsync(newRole, new Claim(Permission, ModifyPermission));
                    }
                    else if (role == SuperUserRole)
                    {
                        await roleManager.AddClaimAsync(newRole, new Claim(Permission, RetrievePermission));
                    }
                    else
                    {
                        await roleManager.AddClaimAsync(newRole, new Claim(Permission, ViewPermission));
                    }
                }
            }

            ApplicationUser admin = new ApplicationUser()...

            ApplicationUser sysAdmin = new ApplicationUser()...;

            PasswordHasher<ApplicationUser> password = new PasswordHasher<ApplicationUser>();

            if (!userManager.Users.Any(u => u.UserName == admin.UserName))
            {
                string hashed = password.HashPassword(admin, configuration["ThePLeagueAdminInitPassword"]);
                admin.PasswordHash = hashed;

                await userManager.CreateAsync(admin);
                await userManager.AddToRoleAsync(admin, AdminRole);
            }

            if (!userManager.Users.Any(u => u.UserName == sysAdmin.UserName))
            {
                string hashed = password.HashPassword(sysAdmin, configuration["ThePLeagueAdminInitPassword"]);
                sysAdmin.PasswordHash = hashed;

                await userManager.CreateAsync(sysAdmin);
                await userManager.AddToRoleAsync(sysAdmin, AdminRole);
            }

            SeedTeams(dbContext);

 }

有什么方法可以使用两个单独的静态异步方法来为数据库播种并使我的上下文保持作用域?

最佳答案

所以我喜欢让事情井井有条并分开。因此我会做类似的事情:

public static class SeedData 
{
    public static void Populate(IServiceProvider services) 
    {
        ApplicationDbContext context = services.GetRequiredService<ApplicationDbContext>();
        if (!context.SomeDbSet.Any()) 
        {
            // ...code omitted for brevity...
        );
        context.SaveChanges();
    }
}
public static class IdentitySeedData 
{
    public static async Task Populate(IServiceProvider services) 
    {
        UserManager<ApplicationUser> userManager = services.GetService<UserManager<ApplicationUser>>();
        RoleManager<IdentityRole> roleManager = services.GetService<RoleManager<IdentityRole>>();
        IConfiguration configuration = services.GetService<IConfiguration>();
        ApplicationDbContext context = services.GetRequiredService<ApplicationDbContext>();

        if (!context.Users.Any()) 
        {
            // ...code omitted for brevity...
            await userManager.CreateAsync(sysAdmin);
            await userManager.AddToRoleAsync(sysAdmin, AdminRole);
        );
        context.SaveChanges();
    }
}

然后是最重要的:

public static class DatabaseInitializer 
{
    public static void Initialize(IServiceProvider services) 
    {
         IdentitySeedData.Populate(services).Wait();
         SeedData.Populate(services);
    }
}

免责声明:我没有运行代码。因此,如果需要进行一些调整,请告诉我。我会做出调整。测试这个有点耗时。

关于asp.net-core - 由于异常 'A second operation started on this context before a previous',无法以静态方法在 ASP.NET Core 中播种数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59539480/

相关文章:

c# - LINQ:分组后从实体集合中选择最小值和最大值

c# - Entity Framework Core 3 原始 SQL 缺少方法

c# - 检查是否启用了延迟加载

c# - 文件/图像上传后 ASP.NET 3.0 mvc 应用程序崩溃

c# - 使用 .NET 核心中的 dapper 批量插入 PostgreSQL

reactjs - 授权然后使用 JWT token 与 Auth0 React 和 ASP.net core

c# - EF Core 3.1 不允许对枚举属性进行包含搜索

c# - 如何使用中间件代替 Controller 初始化?

asp.net-core - 如何解决 Blazor 上的 "TypeError: Failed to fetch"错误?

sql-server - EF Core 添加迁移生成带有 ColumnName1 的额外列