Search code examples
c#asp.net-coredependency-injectionentity-framework-corenpgsql

Accessing NPGSQL in Startup.cs using DI


Using .NET Core 2.1, NPGSQL, Entity Framework and Linux.

From Startups.cs' Configure function, I am calling a function in a dependency injected class that in turn calls another dependency injected class which accesses the DB using Entity Framework + NPGSQL.

Configure Services:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddEntityFrameworkNpgsql()
        .AddDbContext<MMContext>(options => options.UseNpgsql($"Host='localhost'; Port=1234;Database='mydb';Username='test';Password='test'"))
        .BuildServiceProvider();
        services.AddTransient<IMusicManager, MusicManager>();
        services.AddTransient<IMusicRepo, MusicRepo>();

      services.AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

Configure function:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseMvc();

        using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
        {
            var mm = scope.ServiceProvider.GetRequiredService<IMusicManager>();
            mm.DoSomeDBStartupStuff();
        }
    }

IMusicManager

Implementation looks like this:

    private readonly IMusicRepo _musicStoreRepo;
    public MusicManager(IMusicRepo musicStoreRep)
    {
        _musicStoreRepo = musicStoreRepo;
    }

    public void DoSomeDBStartupStuff()
    {
        _musicStoreRepo.InsertSampleStuff();
        _musicStoreRepo.CheckThisAndCheckThat();
    }

IMusicRepo

Implementation looks like this:

    private readonly MMContext _context;
    public MusicRepo(MMContext context)
    {
        _context = context;
    }

    public void InsertSampleStuff()
    {
        _context.Music.AddAsync(new music("abc"));
        _context.Music.AddAsync(new music("123"));
        _context.SaveChangesAsync();
    }

MMContext

This is implemented as so:

public class MMContext : DbContext
{
    public MMContext(DbContextOptions<MMContext> options) : base(options) {}
    ... OnModelCreating etc...
}

I get this exception on firing it up:

Application startup exception: System.InvalidOperationException: Reset() called on connector with state Executing at Npgsql.NpgsqlConnector.Reset() at Npgsql.ConnectorPool.Release(NpgsqlConnector connector) at Npgsql.NpgsqlConnection.Close(Boolean wasBroken) at Npgsql.NpgsqlConnection.Dispose(Boolean disposing) at System.ComponentModel.Component.Dispose() at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.Dispose() at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.Dispose() at Microsoft.EntityFrameworkCore.DbContext.Dispose() at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.Dispose() at MM.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env) in \Startup.cs:line 122 --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.Configure(IApplicationBuilder app) at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.b__0(IApplicationBuilder app) at Microsoft.AspNetCore.Hosting.Internal.AutoRequestServicesStartupFilter.<>c__DisplayClass0_0.b__0(IApplicationBuilder builder) at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()

I am not sure what is causing the issue. Possibly the way I am using Dependency Injection and the way I am using the scope? Help appreciated.


Solution

  • Noticing async calls not being awaited in a void function.

    public void InsertSampleStuff()
    {
        _context.Music.AddAsync(new music("abc"));
        _context.Music.AddAsync(new music("123"));
        _context.SaveChangesAsync();
    }
    

    This could cause threading issues with the DbContext when you try to save those changes.

    Either make the function async and await those calls properly or use the synchronous API

    public void InsertSampleStuff() {
        _context.Music.Add(new music("abc"));
        _context.Music.Add(new music("123"));
        _context.SaveChanges();
    }
    

    If going the asynchronous route then consider moving that setup code into a hosted service and properly awaiting it there

    Reference Reference Background tasks with hosted services in ASP.NET Core