Daily Knowledge Drop
Entity Framework Core's DbContext pooling
functionality can be used to improve the performance of an application.
While generally a lightweight object, each context instance does require some internal setup of various services, which does have an overhead. Context pooling
, as the name implies, create a pool of DbContext instances, setup during startup of the application and reused, thus paying the setup cost only once.
Configuration
Configuring an application to use DbContext pooling
instead of the non-pooling option is incredibly simple - in fact it requires just one small change to the usual DbContext setup.
On startup of an application:
Default non-pooling configuration:
builder.Services.AddDbContext<DemoContext>(o => o .UseSqlServer(builder.Configuration.GetConnectionString("DemoContext")));
DbContext pooling configuration:
builder.Services.AddDbContextPool<DemoContext>(o => o .UseSqlServer(builder.Configuration.GetConnectionString("DemoContext")));
Instead of the AddDbContext
method being called, the AddDbContextPool
method is called - thats all the configuration required.
The default pool size can also be manually specified as part of the AddDbContextPool call (with the default being 1024 in EF Core 6.0 and 128 in previous versions)
Usage
The usage is exactly the same
awith our without the context pooling - inject the application DbContext (DemoContext in the above examples) into the relevent constructor and use it as per normal.
Entity Framework will internally handle everything related to the pooling functionality.
Benchmarks
For the benchmarks, we have a database table with 50
records, and will be comparing retrieving a single row (using the primary key) using:
- Manually created
DbContext
instance each time - A
DbContext
retrieved from the context pool
In the below code, as dependency injection was not used, PooledDbContextFactory
is used to control getting an instance of a DbContext from the pool:
private DbContextOptions<DemoContext> _options;
private PooledDbContextFactory<DemoContext> _poolingFactory;
public Benchmarks()
{
// confiture the dbcontext options
_options = new DbContextOptionsBuilder<DemoContext>()
.UseSqlServer(@"Server=.\SQLEXPRESS;Database=EFPool;Integrated Security=True")
.Options;
// setup the pooling factory using the options
_poolingFactory = new PooledDbContextFactory<DemoContext>(_options);
}
[Benchmark]
public Song WithoutContextPooling()
{
// new DbContext using the options
using var context = new DemoContext(_options);
return context.Songs.First(s => s.Id == 1);
}
[Benchmark]
public Song WithContextPooling()
{
// new DbContext using the PooledDbContextFactory which uses the options
using var context = _poolingFactory.CreateDbContext();
return context.Songs.First(s => s.Id == 1);
}
Results:
Method | Mean | Error | StdDev | Gen 0 | Allocated |
---|---|---|---|---|---|
WithoutContextPooling | 701.3 us | 51.09 us | 147.42 us | - | 96 KB |
WithContextPooling | 124.9 us | 2.15 us | 2.02 us | 1.4648 | 9 KB |
The context pooling results in a 5.5x speed improvement
and 10x memory improvement!
Notes
Improvements such as the ones shown above in the benchmarks above might not be applicable in every application and use case - however my recommendation is to default to using DbContext pooling
. If performance is not where it should be, then benchmark the two options and revert to the option with the best performance.
References
Daily Drop 116: 14-07-2022
At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.
The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.On This Page