ObjectDisposedException on a ASP.NET Core 2.0 MVC custom database initializer class

2

I'm getting the following exception in my custom database initializer class:

System.ObjectDisposedException occurred HResult=0x80131622
Message=Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances. Source= StackTrace: at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed() at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider() at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies() at Microsoft.EntityFrameworkCore.DbContext.d__48.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore9.<CreateAsync>d__22.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter
1.GetResult() at Microsoft.AspNetCore.Identity.UserManager1.<CreateAsync>d__73.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter
1.GetResult() at Microsoft.AspNetCore.Identity.UserManager1.<CreateAsync>d__78.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at LlabApp.Data.DbInitializer.d__4.MoveNext() in E:\C# projects\Atenea\LlabApp\LlabApp\LlabApp\Data\DbInitializer.cs:line 58

Here is the Initializer:

public class DbInitializer : IDbInitializer
{
    private readonly ApplicationDbContext _Context;
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly RoleManager<ApplicationRole> _roleManager;

    public DbInitializer(
        ApplicationDbContext context,
        UserManager<ApplicationUser> userManager,
        RoleManager<ApplicationRole> roleManager)
    {
        _context = context;
        _userManager = userManager;
        _roleManager = roleManager;
    }

    public async void Initialize()
    {
        var adminUsers = _userManager.GetUsersInRoleAsync(RoleClaimValues.Admin).Result;
        if (adminUsers == null || adminUsers.Count == 0)
        {
(...)
        }
     }
}

public interface IDbInitializer
{
    void Initialize();
}

And here is the startup.cs where i add the scope of IDbInitializer in ConfigureService and then call it from Configure:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }


    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("ApplicationConnection")));

        services.AddIdentity<ApplicationUser, ApplicationRole>(o => {
            o.Password.RequireDigit = false;
            o.Password.RequiredLength = 4;
            o.Password.RequireLowercase = false;
            o.Password.RequireUppercase = false;
            o.Password.RequireNonAlphanumeric = false;
            o.User.RequireUniqueEmail = true;
        })
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        services.AddMvc();

        // Add application services.
        services.AddTransient<IEmailSender, MessageServices>();
        //Seed database
        services.AddScoped<IDbInitializer, DbInitializer>();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IDbInitializer dbInitializer)
    {
        loggerFactory.AddConsole();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();

            app.UseBrowserLink();
            app.UseDatabaseErrorPage();

            app.UseStatusCodePages("text/plain", "Status code page, status code: {0}");
        }
        else
        {
            app.UseExceptionHandler("/Error");
        }

        app.UseStaticFiles();
        app.UseAuthentication();

        dbInitializer.Initialize();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

I don't know why the exception tells that the problem could be that the context is disposed somewhere.

I've also added

.UseDefaultServiceProvider(options =>
  options.ValidateScopes = false)

after .UseStartup<Startup>() just in case it was something relative to the scopes, but the exception is the same.

c#
asp.net-core-2.0
asked on Stack Overflow Sep 9, 2017 by MorgoZ

2 Answers

4

I had the same problem as you had. You indeed have to change all code to return a Task. Otherwise the DbContext is disposed before all actions are done.

Database Initialization is changed in dotnet core 2.0. I created a blog about this specific topic. See http://www.locktar.nl/programming/net-core/seed-database-users-roles-dotnet-core-2-0-ef/ for more information.

answered on Stack Overflow Sep 19, 2017 by LockTar
3

Ok, the problem is with this line:

public async void Initialize()

since it is an async method it has awaits inside, but because it's void, the ServiceProvider disposes the dependecies after reaching the first await and leaving the context when getting back to the method call from the startup and continuing the execution.

So, the solution is to change void to Task at DbInitializer and IDbInitializer, and casting dbInitializer parameter in startap and waiting for it to finish:

((DbInitializer)dbInitializer).Initialize().Wait();

Of course it is not very usefull to call async methods and then wait for all of them to finish, but i don't know if it is possible to set the Configure method an async method... so that is the solution i found and since it is just for seeding the database at Develop environment i'm happy with this.

Any further advise will be appreciated.

answered on Stack Overflow Sep 9, 2017 by MorgoZ

User contributions licensed under CC BY-SA 3.0