Entity Framework DbContext with pooling v without pooling with EF 8 on .NET 8
Date Added (UTC):
07 Apr 2024 @ 20:06
Date Updated (UTC):12 Apr 2024 @ 02:03
.NET Version(s): Tag(s):
Added By:
.NET Developer and tech lead from Ireland!
Benchmark Results:
Benchmark Code:
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
[MemoryDiagnoser]
public class ContextPooling
{
private DbContextOptions<BloggingContext> _options;
private PooledDbContextFactory<BloggingContext> _poolingFactory;
[Params(1)]
public int NumBlogs { get; set; }
[GlobalSetup]
public void Setup()
{
_options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(@"Server=localhost;Database=Blogging;Trusted_Connection=True;TrustServerCertificate=true")
.Options;
using var context = new BloggingContext(_options);
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.SeedData(NumBlogs);
_poolingFactory = new PooledDbContextFactory<BloggingContext>(_options);
}
[Benchmark]
public List<Blog> WithoutContextPooling()
{
using var context = new BloggingContext(_options);
return context.Blogs.ToList();
}
[Benchmark]
public List<Blog> WithContextPooling()
{
using var context = _poolingFactory.CreateDbContext();
return context.Blogs.ToList();
}
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public BloggingContext(DbContextOptions options) : base(options) { }
public void SeedData(int numBlogs)
{
Blogs.AddRange(Enumerable.Range(0, numBlogs).Select(i => new Blog { Url = $"http://www.someblog{i}.com" }));
SaveChanges();
}
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public int Rating { get; set; }
}
}
Powered by SharpLab
// .NET 8 Lowered C# Code unavailable due to errors:
error CS0234: The type or namespace name 'EntityFrameworkCore' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)
error CS0246: The type or namespace name 'DbContextOptions<>' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'PooledDbContextFactory<>' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbContext' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbSet<>' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbContextOptions' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbContextOptionsBuilder<>' could not be found (are you missing a using directive or an assembly reference?)
error CS1674: 'ContextPooling.BloggingContext': type used in a using statement must be implicitly convertible to 'System.IDisposable'.
error CS1061: 'ContextPooling.BloggingContext' does not contain a definition for 'Database' and no accessible extension method 'Database' accepting a first argument of type 'ContextPooling.BloggingContext' could be found (are you missing a using directive or an assembly reference?)
error CS1729: 'DbContext' does not contain a constructor that takes 1 arguments
error CS0103: The name 'SaveChanges' does not exist in the current context
Powered by SharpLab
// .NET 8 IL Code unavailable due to errors:
error CS0234: The type or namespace name 'EntityFrameworkCore' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)
error CS0246: The type or namespace name 'DbContextOptions<>' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'PooledDbContextFactory<>' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbContext' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbSet<>' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbContextOptions' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbContextOptionsBuilder<>' could not be found (are you missing a using directive or an assembly reference?)
error CS1674: 'ContextPooling.BloggingContext': type used in a using statement must be implicitly convertible to 'System.IDisposable'.
error CS1061: 'ContextPooling.BloggingContext' does not contain a definition for 'Database' and no accessible extension method 'Database' accepting a first argument of type 'ContextPooling.BloggingContext' could be found (are you missing a using directive or an assembly reference?)
error CS1729: 'DbContext' does not contain a constructor that takes 1 arguments
error CS0103: The name 'SaveChanges' does not exist in the current context
Powered by SharpLab
|
Benchmark Description:
Although creating DbContext objects is really inexpensive, in really high performance scenarios it may be beneficial to switch to using Entity Frameworks DbContext pooling feature.
In this case EF Core will pool your context instances: when you dispose your context, EF Core resets its state and stores it in an internal pool; when a new instance is next requested, that pooled instance is returned instead of setting up a new one. Context pooling allows you to **pay context setup costs only once** at program startup.
We can see above that memory allocation is a lot less with the pooling approach.
This benchmark was taken on EF 8.0.3
[DbContext pooling docs on Microsoft learn](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics?tabs=with-di%2Cexpression-api-with-constant#dbcontext-pooling)
The provided benchmark code is designed to measure and compare the performance of creating and using instances of `DbContext` in Entity Framework Core (EF Core) with and without context pooling. This benchmark is particularly relevant for applications that frequently create and dispose of `DbContext` instances, as it can have a significant impact on performance and resource utilization.
### General Setup
- **.NET Version**: The specific .NET version isn't mentioned in the provided code, but it's essential to use a version compatible with EF Core and BenchmarkDotNet. EF Core supports .NET Standard 2.0 and above, so it can be run on various .NET implementations (e.g., .NET Core 3.1, .NET 5, .NET 6).
- **Configuration**: The benchmark uses `BenchmarkDotNet`, a powerful library for benchmarking .NET code, with the `[MemoryDiagnoser]` attribute to measure memory allocations. The `DbContextOptions` for `BloggingContext` are configured to use SQL Server with a connection string pointing to a local database named `Blogging`.
- **DbContext Pooling**: The benchmark compares the performance of using a standard `DbContext` instance (`WithoutContextPooling`) versus using a pooled `DbContext` instance (`WithContextPooling`). `DbContext` pooling is a feature in EF Core that can improve performance by reusing `DbContext` instances from a pool instead of creating new instances each time.
### Benchmark Methods
#### WithoutContextPooling
- **Purpose**: This method measures the performance of creating a new `DbContext` instance, querying the `Blogs` DbSet to list all blogs, and then disposing of the `DbContext` instance. This simulates a common scenario without the use of context pooling.
- **Performance Aspect**: It tests the overhead of creating and disposing of `DbContext` instances without pooling, including the time and memory allocations involved in initializing a new `DbContext`, executing a query, and then cleaning up.
- **Expected Results**: Running this benchmark will provide insights into the cost (in terms of execution time and memory usage) of not using `DbContext` pooling. It's expected to be slower and allocate more memory compared to using context pooling, especially under high load or in scenarios where `DbContext` instances are frequently created and disposed.
#### WithContextPooling
- **Purpose**: This method evaluates the performance benefits of using `DbContext` pooling by obtaining a `DbContext` instance from a pool, performing the same query operation as the previous method, and then returning the instance to the pool.
- **Performance Aspect**: It specifically measures how much performance improvement can be achieved by reusing `DbContext` instances from a pool, focusing on reduced execution time and memory allocations due to the reuse of `DbContext` instances.
- **Expected Results**: This benchmark is expected to show better performance in terms of faster execution times and lower memory allocations compared to not using context pooling. The degree of improvement can vary based on the workload, the size of the pool, and other factors. It demonstrates the efficiency gains from avoiding the overhead of repeatedly creating and disposing of `DbContext` instances.
### Conclusion
By comparing the results of these two benchmark methods, developers can understand the impact of `DbContext` pooling on the performance and resource utilization of applications using EF Core. This can guide decisions on whether to enable `DbContext` pooling based on the specific requirements and usage patterns of the application.