Using Entity Framework Core DbContext Pooling with Simple Injector

Viewed 9958

Looking at the examples of how to use db context pool I see it was designed to be used with ServiceCollection:

var serviceProvider = new ServiceCollection()
    .AddDbContextPool<AdventureWorksContext>(options => { //options })
    .BuildServiceProvider();

But what about Simple Injector? is it possible to register DB pooling in the Simple Injector container?

p.s. My app is not ASP.NET MVC, it's just a kinda DAL

3 Answers

EF Core DbContext pooling in ASP.NET Core

When integrating Simple Injector in ASP.NET Core, you keep framework and third-party components inside the .NET Core configuration system. This means that enabling Entity Framework Core context pooling is done exactly as Microsoft documents it:

services.AddDbContextPool<BloggingContext>(
    options => options.UseSqlServer(connectionString));

As Simple Injector does not replace the built-in configuration system, you will have to instruct Simple Injector to automatically load missing registrations (such as your DbContext) from the .NET Core configuration system. This can be done by using the AddSimpleInjector and UseSimpleInjector extension methods, as shown here.

private SimpleInjector.Container container;

public void ConfigureServices(IServiceCollection services)
{
    ...
        
    services.AddDbContextPool<BloggingContext>(
        options => options.UseSqlServer(connectionString));        

    services.AddSimpleInjector(container, options =>
    {
        options.AddAspNetCore();
    });
}

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

    container.Verify();

    ...
}

Using this setup, the BloggingContext can be injected into any component that is resolved from Simple Injector, while the BloggingContext is pooled by Entity Framework. For intance:

// Application compoment registered in and resolved from Simple Injector
public class CommentIsNoSpamValidator : IValidator<PostComment>
{
    private readonly BloggingContext context;

    // Is injected with BloggingContext from IServiceCollection
    public CommentIsNoSpamValidator(BloggingContext context)
    {
        this.context = context;
    }

    public IEnumerable<ValidationResult> Validate(PostComment command)
    {
        // Complex business logic here.
    }
}

EF Core DbContext pooling in a .NET (Core) Console application

When it comes to using Entity Framework Core context pooling in a .NET Core console application, the solution will be very similar, although you will have to set up a little bit more:

public void Main()
{
    var container = new Container();

    var services = new ServiceCollection();
    
    services.AddDbContextPool<BloggingContext>(
        options => options.UseSqlServer(connectionString));
            
    services.AddSimpleInjector(container);

    services
        .BuildServiceProvider(validateScopes: true)
        .UseSimpleInjector(container);

    container.Verify();

    // Run application code
    using (AsyncScopedLifestyle.BeginScope(container))
    {
        var service = container.GetInstance<MainService>();
        service.DoAwesomeStuff();
    }
}

So in the end, the DbContext's lifetime is managed by the MS.DI scope, but that scope is managed by Simple Injector's scope.

EF Core DbContext pooling in a library

In case you are building a library, i.e. a non-startup project, please stop what you're doing. Only the application's startup assembly should have a Composition Root, and only the Composition Root should use a DI Container (such as Simple Injector or MS.DI's ServiceCollection). All other libraries in your application should stay oblivious of the (possible) existence of a Container.

You could use

container.Register(() => serviceProvider.GetRequiredService<AdventureWorksContext>());

to have the ServiceProvider resolve the dependency as it's requested.

Adding to @Steven's excellent answer, here's his console application answer, but with the context (implicitly) using UseInternalServiceProvider.

var services = new ServiceCollection();
services.AddEntityFrameworkSqlServer();
services.AddSingleton<Microsoft.Extensions.Logging.ILoggerFactory>(new Microsoft.Extensions.Logging.LoggerFactory());
services.AddSingleton<IInterface>(new MyImplemenation());

services.AddDbContextPool<EFViewAndManyToManyDb>(optionsBuilder => {
  optionsBuilder.UseSqlServer("");
});

services.AddSimpleInjector(container);

services
  .BuildServiceProvider(validateScopes: true)
  .UseSimpleInjector(container);

Here's why you'd want to use UseInternalServiceProvider. TLDR: if your DbContext has a dependencies, e.g. IInterface. You'll also need to add the SimpleInjector.Integration.ServiceCollection Nuget package.

Related