Skip to content

Commit e38bd38

Browse files
authored
Improvements to context pooling docs (#3460)
Closes #3445 Closes #3369
1 parent 96142cc commit e38bd38

File tree

8 files changed

+134
-18
lines changed

8 files changed

+134
-18
lines changed

entity-framework/core/performance/advanced-performance-topics.md

+28-10
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,53 @@ uid: core/performance/advanced-performance-topics
1010

1111
## DbContext pooling
1212

13-
`AddDbContextPool` enables pooling of `DbContext` instances. Context pooling can increase throughput in high-scale scenarios such as web servers by reusing context instances, rather than creating new instances for each request.
13+
A `DbContext` is generally a light object: creating and disposing one doesn't involve a database operation, and most applications can do so without any noticeable impact on performance. However, each `DbContext` does set up a various internal services and objects necessary for performing its duties, and the overhead of continuously doing so may be significant in high-performance scenarios. For these cases, EF Core can *pool* your `DbContext` instances: when you dispose your `DbContext`, 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. `DbContext` pooling allows you to pay `DbContext` setup costs only once at program startup, rather than continuously.
1414

15-
The typical pattern in an ASP.NET Core app using EF Core involves registering a custom <xref:Microsoft.EntityFrameworkCore.DbContext> type into the [dependency injection](/aspnet/core/fundamentals/dependency-injection) container and obtaining instances of that type through constructor parameters in controllers or Razor Pages. Using constructor injection, a new context instance is created for each request.
15+
Following are the benchmark results for fetching a single row from a SQL Server database running locally on the same machine, with and without `DbContext` pooling. As always, results will change with the number of rows, the latency to your database server and other factors. Importantly, this benchmarks single-threaded pooling performance, while a real-world contended scenario may have different results; benchmark on your platform before making any decisions. [The source code is available here](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Benchmarks/ContextPooling.cs), feel free to use it as a basis for your own measurements.
1616

17-
<xref:Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.AddDbContextPool%2A> enables a pool of reusable context instances. To use context pooling, use the `AddDbContextPool` method instead of `AddDbContext` during service registration:
17+
| Method | NumBlogs | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
18+
|---------------------- |--------- |---------:|---------:|---------:|--------:|------:|------:|----------:|
19+
| WithoutContextPooling | 1 | 701.6 us | 26.62 us | 78.48 us | 11.7188 | - | - | 50.38 KB |
20+
| WithContextPooling | 1 | 350.1 us | 6.80 us | 14.64 us | 0.9766 | - | - | 4.63 KB |
21+
22+
Note that `DbContext` pooling is orthogonal to database connection pooling, which is managed at a lower level in the database driver.
23+
24+
### [With dependency injection](#tab/with-di)
25+
26+
The typical pattern in an ASP.NET Core app using EF Core involves registering a custom <xref:Microsoft.EntityFrameworkCore.DbContext> type into the [dependency injection](/aspnet/core/fundamentals/dependency-injection) container via <xref:Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.AddDbContext%2A>. Then, instances of that type are obtained through constructor parameters in controllers or Razor Pages.
27+
28+
To enable `DbContext` pooling, simply replace `AddDbContext` with <xref:Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.AddDbContextPool%2A>:
1829

1930
```csharp
2031
services.AddDbContextPool<BloggingContext>(
2132
options => options.UseSqlServer(connectionString));
2233
```
2334

24-
When `AddDbContextPool` is used, at the time a context instance is requested, EF first checks if there is an instance available in the pool. Once the request processing finalizes, any state on the instance is reset and the instance is itself returned to the pool.
35+
The `poolSize` parameter of <xref:Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.AddDbContextPool%2A> sets the maximum number of instances retained by the pool (defaults to 1024 in EF Core 6.0, and to 128 in previous versions). Once `poolSize` is exceeded, new context instances are not cached and EF falls back to the non-pooling behavior of creating instances on demand.
2536

26-
This is conceptually similar to how connection pooling operates in ADO.NET providers and has the advantage of saving some of the cost of initialization of the context instance.
37+
### [Without dependency injection](#tab/without-di)
2738

28-
The `poolSize` parameter of <xref:Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.AddDbContextPool%2A> sets the maximum number of instances retained by the pool (defaults to 1024 in EF Core 6.0, and to 128 in previous versions). Once `poolSize` is exceeded, new context instances are not cached and EF falls back to the non-pooling behavior of creating instances on demand.
39+
> [!NOTE]
40+
> Pooling without dependency injection was introduced in EF Core 6.0.
2941
30-
### Limitations
42+
To use `DbContext` pooling without dependency injection, initialize a `PooledDbContextFactory` and request context instances from it:
43+
44+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#DbContextPoolingWithoutDI)]
45+
46+
The `poolSize` parameter of the `PooledDbContextFactory` constructor sets the maximum number of instances retained by the pool (defaults to 1024 in EF Core 6.0, and to 128 in previous versions). Once `poolSize` is exceeded, new context instances are not cached and EF falls back to the non-pooling behavior of creating instances on demand.
3147

32-
Apps should be profiled and tested to show that context initialization is a significant cost.
48+
***
49+
50+
### Limitations
3351

34-
`AddDbContextPool` has a few limitations on what can be done in the `OnConfiguring` method of the context.
52+
`DbContext` pooling has a few limitations on what can be done in the `OnConfiguring` method of the context.
3553

3654
> [!WARNING]
3755
> Avoid using context pooling in apps that maintain state. For example, private fields in the context that shouldn't be shared across requests. EF Core only resets the state that it is aware of before adding a context instance to the pool.
3856
3957
Context pooling works by reusing the same context instance across requests. This means that it's effectively registered as a [Singleton](/aspnet/core/fundamentals/dependency-injection#service-lifetimes) in terms of the instance itself so that it's able to persist.
4058

41-
Context pooling is intended for scenarios where the context configuration, which includes services resolved, is fixed between requests. For cases where [Scoped](/aspnet/core/fundamentals/dependency-injection#service-lifetimes) services are required, or configuration needs to be changed, don't use pooling. The performance gain from pooling is usually negligible except in highly optimized scenarios.
59+
Context pooling is intended for scenarios where the context configuration, which includes services resolved, is fixed between requests. For cases where [Scoped](/aspnet/core/fundamentals/dependency-injection#service-lifetimes) services are required, or configuration needs to be changed, don't use pooling.
4260

4361
## Query caching and parameterization
4462

entity-framework/core/providers/sql-server/columns.md

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ This page details column configuration options that are specific to the SQL Serv
1111

1212
## Sparse columns
1313

14+
> [!NOTE]
15+
> Sparse column support was introduced in EF Core 6.0.
16+
1417
Sparse columns are ordinary columns that have an optimized storage for null values, reducing the space requirements for null values at the cost of more overhead to retrieve non-null values.
1518

1619
As an example, consider a type hierarchy mapped via [the table-per-hierarchy (TPH) strategy](xref:core/modeling/inheritance#table-per-hierarchy-and-discriminator-configuration). In TPH, a single database table is used to hold all types in a hierarchy; this means that the table must contain columns for each and every property across the entire hierarchy, and for columns belonging to rare types, most rows will contain a null value for that column. In these cases, it may make sense to configure the column as *sparse*, in order to reduce the space requirements. The decision whether to make a column sparse must be made by the user, and depends on expectations for actual data in the table.

entity-framework/core/what-is-new/ef-core-6.0/breaking-changes.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ If your application expects joined entities to be returned in a particular order
123123

124124
<xref:Microsoft.EntityFrameworkCore.DbSet%601> was originally made to implement <xref:System.Collections.Generic.IAsyncEnumerable%601> mainly in order to allow direct enumeration on it via the `foreach` construct. Unfortunately, when a project also references [System.Linq.Async](https://www.nuget.org/packages/System.Linq.Async) in order to compose async LINQ operators client-side, this resulted in an ambiguous invocation error between the operators defined over `IQueryable<T>` and those defined over `IAsyncEnumerable<T>`. C# 9 added [extension `GetEnumerator` support for `foreach` loops](/dotnet/csharp/language-reference/proposals/csharp-9.0/extension-getenumerator), removing the original main reason to reference `IAsyncEnumerable`.
125125

126-
The vast majority of `DbSet` usages will continue to work as-is, since they either compose LINQ operators over `DbSet`, enumerate it directly, etc. The only usages broken are those which attempt to cast `DbSet` directly to `IAsyncEnumerable`.
126+
The vast majority of `DbSet` usages will continue to work as-is, since they compose LINQ operators over `DbSet`, enumerate it, etc. The only usages broken are those which attempt to cast `DbSet` directly to `IAsyncEnumerable`.
127127

128128
#### Mitigations
129129

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
4-
<TargetFramework>net5.0</TargetFramework>
4+
<TargetFramework>net6.0</TargetFramework>
55
<Optimize>true</Optimize>
66
</PropertyGroup>
77

88
<ItemGroup>
99
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
10-
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.2" />
11-
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="5.0.2" />
10+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0-rc.1.21452.10" />
11+
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="6.0.0-rc.1.21452.10" />
1212
</ItemGroup>
1313
</Project>
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using BenchmarkDotNet.Attributes;
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Infrastructure;
6+
7+
namespace Benchmarks
8+
{
9+
[MemoryDiagnoser]
10+
public class ContextPooling
11+
{
12+
private DbContextOptions<BloggingContext> _options;
13+
private PooledDbContextFactory<BloggingContext> _poolingFactory;
14+
15+
[Params(1)]
16+
public int NumBlogs { get; set; }
17+
18+
[GlobalSetup]
19+
public void Setup()
20+
{
21+
_options = new DbContextOptionsBuilder<BloggingContext>()
22+
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True")
23+
.Options;
24+
25+
using var context = new BloggingContext(_options);
26+
context.Database.EnsureDeleted();
27+
context.Database.EnsureCreated();
28+
context.SeedData(NumBlogs);
29+
30+
_poolingFactory = new PooledDbContextFactory<BloggingContext>(_options);
31+
}
32+
33+
[Benchmark]
34+
public List<Blog> WithoutContextPooling()
35+
{
36+
using var context = new BloggingContext(_options);
37+
38+
return context.Blogs.ToList();
39+
}
40+
41+
[Benchmark]
42+
public List<Blog> WithContextPooling()
43+
{
44+
using var context = _poolingFactory.CreateDbContext();
45+
46+
return context.Blogs.ToList();
47+
}
48+
49+
public class BloggingContext : DbContext
50+
{
51+
public DbSet<Blog> Blogs { get; set; }
52+
53+
public BloggingContext(DbContextOptions options) : base(options) {}
54+
55+
public void SeedData(int numBlogs)
56+
{
57+
Blogs.AddRange(Enumerable.Range(0, numBlogs).Select(i => new Blog { Url = $"http://www.someblog{i}.com"}));
58+
SaveChanges();
59+
}
60+
}
61+
62+
public class Blog
63+
{
64+
public int BlogId { get; set; }
65+
public string Url { get; set; }
66+
public int Rating { get; set; }
67+
}
68+
}
69+
}

samples/core/Performance/Performance.csproj

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net5.0</TargetFramework>
5+
<TargetFramework>net6.0</TargetFramework>
66
</PropertyGroup>
77

88
<ItemGroup>
9-
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.2" />
10-
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="5.0.2" />
11-
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
9+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0-rc.1.21452.10" />
10+
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="6.0.0-rc.1.21452.10" />
11+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0-rc.1.21451.13" />
1212
</ItemGroup>
1313

1414
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace Performance
4+
{
5+
public class PooledBloggingContext : DbContext
6+
{
7+
public PooledBloggingContext(DbContextOptions options) : base(options) {}
8+
9+
public DbSet<Blog> Blogs { get; set; }
10+
public DbSet<Post> Posts { get; set; }
11+
}
12+
}

samples/core/Performance/Program.cs

+14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Infrastructure;
56
using Performance.LazyLoading;
67

78
namespace Performance
@@ -203,6 +204,19 @@ private static void Main(string[] args)
203204
context.Database.ExecuteSqlRaw("UPDATE [Employees] SET [Salary] = [Salary] + 1000");
204205
#endregion
205206
}
207+
208+
#region DbContextPoolingWithoutDI
209+
var options = new DbContextOptionsBuilder<PooledBloggingContext>()
210+
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True")
211+
.Options;
212+
213+
var factory = new PooledDbContextFactory<PooledBloggingContext>(options);
214+
215+
using (var context = factory.CreateDbContext())
216+
{
217+
var allPosts = context.Posts.ToList();
218+
}
219+
#endregion
206220
}
207221
}
208222
}

0 commit comments

Comments
 (0)