Skip to content
Open
109 changes: 109 additions & 0 deletions entity-framework/core/dbcontext-configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ The following table contains examples of common methods called on `DbContextOpti
| <xref:Microsoft.EntityFrameworkCore.DbContextOptionsBuilder.EnableDetailedErrors*> | More detailed query errors (at the expense of performance) | [Logging, Events, and Diagnostics](xref:core/logging-events-diagnostics/index)
| <xref:Microsoft.EntityFrameworkCore.DbContextOptionsBuilder.ConfigureWarnings*> | Ignore or throw for warnings and other events | [Logging, Events, and Diagnostics](xref:core/logging-events-diagnostics/index)
| <xref:Microsoft.EntityFrameworkCore.DbContextOptionsBuilder.AddInterceptors*> | Registers EF Core interceptors | [Logging, Events, and Diagnostics](xref:core/logging-events-diagnostics/index)
| <xref:Microsoft.EntityFrameworkCore.DbContextOptionsBuilder.EnableServiceProviderCaching*> | Controls caching of the internal service provider | [Service Provider Caching](#service-provider-caching)
| <xref:Microsoft.EntityFrameworkCore.ProxiesExtensions.UseLazyLoadingProxies*> | Use dynamic proxies for lazy-loading | [Lazy Loading](xref:core/querying/related-data/lazy)
| <xref:Microsoft.EntityFrameworkCore.ProxiesExtensions.UseChangeTrackingProxies*> | Use dynamic proxies for change-tracking | Coming soon...

Expand Down Expand Up @@ -423,6 +424,114 @@ Any code that explicitly executes multiple threads in parallel should ensure tha

Using dependency injection, this can be achieved by either registering the context as scoped, and creating scopes (using `IServiceScopeFactory`) for each thread, or by registering the `DbContext` as transient (using the overload of `AddDbContext` which takes a `ServiceLifetime` parameter).

## Service Provider Caching

EF Core uses an internal service provider to manage services required for database operations, including query compilation, model building, and other core functionality. By default, EF Core caches these internal service providers to improve performance when multiple `DbContext` instances share the same configuration.

### EnableServiceProviderCaching

The <xref:Microsoft.EntityFrameworkCore.DbContextOptionsBuilder.EnableServiceProviderCaching*> method controls whether EF Core caches the internal service provider:

<!--
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.EnableServiceProviderCaching(false)
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test");
}
}
-->
[!code-csharp[EnableServiceProviderCaching](../../../samples/core/Miscellaneous/CompiledModels/BlogsContext.cs?range=18-22&highlight=2)]

**Default behavior**: Service provider caching is **enabled by default** (`true`). This means:
- Service providers are cached and reused across `DbContext` instances with the same configuration
- Better performance for applications that create many `DbContext` instances
- Lower memory overhead when multiple contexts share configurations

**When to disable caching**: You might want to disable service provider caching (`false`) in these scenarios:
- **Testing environments**: To ensure each test gets a fresh service provider
- **Compiled models**: When using [compiled models](xref:core/performance/advanced-performance-topics#compiled-models), caching may not provide benefits
- **Dynamic configurations**: When `DbContext` configurations change dynamically at runtime
- **Memory-sensitive applications**: When you want to minimize memory usage and don't mind the performance cost

### Memory Cache Integration

EF Core integrates with ASP.NET Core's memory caching infrastructure through `IMemoryCache`. This is separate from the internal service provider caching described above.

#### EF Core 3.0+ Memory Cache Changes

Starting with EF Core 3.0, there were important changes to how memory caching is handled:

**AddDbContext and Memory Cache**: Prior to EF Core 3.0, calling `AddDbContext` or `AddDbContextPool` automatically registered `IMemoryCache` services. Starting with EF Core 3.0, these methods no longer automatically call `AddMemoryCache()`.

**If your application needs `IMemoryCache`**, you must explicitly register it:

```csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache(); // Explicit registration required in EF Core 3.0+
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
}
```

**AddEntityFramework* Methods**: The `AddEntityFramework*` methods (like `AddEntityFrameworkSqlServer`) now register `IMemoryCache` with a size limit to prevent unbounded memory growth:

```csharp
public void ConfigureServices(IServiceCollection services)
{
// This automatically registers IMemoryCache with size limits
services.AddEntityFrameworkSqlServer();

// Or register your own IMemoryCache implementation beforehand
services.AddMemoryCache(options =>
{
options.SizeLimit = 1000; // Custom size limit
});
services.AddEntityFrameworkSqlServer();
}
```

### Performance Considerations

**Service Provider Caching Performance Impact**:
- **Enabled (default)**: Faster `DbContext` creation, lower memory usage for multiple instances
- **Disabled**: Slower `DbContext` creation, but more predictable memory usage

**Memory Cache Performance Impact**:
- EF Core uses `IMemoryCache` for various internal caching operations
- Query plan caching relies on memory caching infrastructure
- Without proper memory cache configuration, you may experience degraded performance

### Best Practices

1. **Keep service provider caching enabled** unless you have a specific reason to disable it
2. **Explicitly register `IMemoryCache`** in EF Core 3.0+ applications that need it
3. **Configure memory cache size limits** to prevent unbounded memory growth
4. **Disable service provider caching in tests** to ensure isolation between test runs
5. **Monitor memory usage** in applications with dynamic configurations

### Example: Configuring Both Caching Options

```csharp
public void ConfigureServices(IServiceCollection services)
{
// Configure memory cache with size limit
services.AddMemoryCache(options =>
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove unneeded curlies

options.SizeLimit = 1000;
options.CompactionPercentage = 0.25;
});

services.AddDbContext<ApplicationDbContext>(options =>
options
.UseSqlServer(connectionString)
.EnableServiceProviderCaching(true)); // Default, but shown for clarity
}
```

## More reading

- Read [Dependency Injection](/aspnet/core/fundamentals/dependency-injection) to learn more about using DI.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.11" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
</ItemGroup>

</Project>
180 changes: 180 additions & 0 deletions samples/core/Miscellaneous/CachingConfiguration/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace CachingConfiguration;

class Program
{
static void Main()
{
Console.WriteLine("=== EF Core Memory Cache Configuration Examples ===\n");

// Example 1: Service Provider Caching (Default behavior)
ServiceProviderCachingExample();

// Example 2: Disable Service Provider Caching
DisableServiceProviderCachingExample();

// Example 3: ASP.NET Core with Memory Cache
AspNetCoreMemoryCacheExample();

// Example 4: Performance comparison
PerformanceComparisonExample();
}

static void ServiceProviderCachingExample()
{
Console.WriteLine("1. Service Provider Caching (Default - Enabled)");
Console.WriteLine("================================================");

// Create multiple contexts with same configuration
// Service providers will be cached and reused
var context1 = new BlogContextWithCaching();
var context2 = new BlogContextWithCaching();

Console.WriteLine($"Context 1 created: {context1.GetHashCode()}");
Console.WriteLine($"Context 2 created: {context2.GetHashCode()}");
Console.WriteLine("Service providers are cached and reused for same configuration\n");

context1.Dispose();
context2.Dispose();
}

static void DisableServiceProviderCachingExample()
{
Console.WriteLine("2. Service Provider Caching Disabled");
Console.WriteLine("====================================");

// Create contexts with caching disabled
// Each context gets its own service provider
var context1 = new BlogContextNoCaching();
var context2 = new BlogContextNoCaching();

Console.WriteLine($"Context 1 created: {context1.GetHashCode()}");
Console.WriteLine($"Context 2 created: {context2.GetHashCode()}");
Console.WriteLine("Each context has its own service provider (no caching)\n");

context1.Dispose();
context2.Dispose();
}

static void AspNetCoreMemoryCacheExample()
{
Console.WriteLine("3. ASP.NET Core Memory Cache Configuration");
Console.WriteLine("==========================================");

var services = new ServiceCollection();

// Register memory cache with custom configuration
services.AddMemoryCache(options =>
{
options.SizeLimit = 1000;
options.CompactionPercentage = 0.25;
});

// Register DbContext with explicit memory cache
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("TestDb")
.EnableServiceProviderCaching(true) // Default, shown for clarity
.LogTo(Console.WriteLine, LogLevel.Information));

var serviceProvider = services.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();

var context = scope.ServiceProvider.GetRequiredService<BlogContext>();
var memoryCache = scope.ServiceProvider.GetRequiredService<IMemoryCache>();

Console.WriteLine($"DbContext created: {context.GetType().Name}");
Console.WriteLine($"Memory cache available: {memoryCache != null}");
Console.WriteLine("Memory cache is configured with size limit of 1000\n");
}

static void PerformanceComparisonExample()
{
Console.WriteLine("4. Performance Comparison");
Console.WriteLine("=========================");

const int iterations = 1000;

// Test with caching enabled
var sw = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
using var context = new BlogContextWithCaching();
// Simulate some work
_ = context.Model;
}
sw.Stop();
var cachedTime = sw.ElapsedMilliseconds;

// Test with caching disabled
sw.Restart();
for (int i = 0; i < iterations; i++)
{
using var context = new BlogContextNoCaching();
// Simulate some work
_ = context.Model;
}
sw.Stop();
var noCacheTime = sw.ElapsedMilliseconds;

Console.WriteLine($"Time with service provider caching: {cachedTime}ms");
Console.WriteLine($"Time without service provider caching: {noCacheTime}ms");
Console.WriteLine($"Performance improvement: {((double)noCacheTime / cachedTime):F1}x faster\n");
}
}

// Context with default caching (enabled)
public class BlogContextWithCaching : DbContext
{
public DbSet<Blog> Blogs => Set<Blog>();

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseInMemoryDatabase("CachedDb")
.EnableServiceProviderCaching(true); // Default behavior, explicit for clarity
}
}

// Context with caching disabled
public class BlogContextNoCaching : DbContext
{
public DbSet<Blog> Blogs => Set<Blog>();

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseInMemoryDatabase("NoCacheDb")
.EnableServiceProviderCaching(false); // Explicitly disable caching
}
}

// Base context for DI scenarios
public class BlogContext : DbContext
{
public BlogContext(DbContextOptions<BlogContext> options) : base(options)
{
}

public DbSet<Blog> Blogs => Set<Blog>();
}

public class Blog
{
public int Id { get; set; }
public string Title { get; set; } = null!;
public List<Post> Posts { get; set; } = new();
}

public class Post
{
public int Id { get; set; }
public string Content { get; set; } = null!;
public int BlogId { get; set; }
public Blog Blog { get; set; } = null!;
}
38 changes: 38 additions & 0 deletions samples/core/Miscellaneous/CachingConfiguration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# EF Core Caching Configuration Sample

This sample demonstrates various aspects of memory caching and service provider caching in Entity Framework Core.

## What This Sample Shows

1. **Service Provider Caching (Default)**: How EF Core caches internal service providers for better performance
2. **Disabled Service Provider Caching**: When and how to disable caching for specific scenarios
3. **ASP.NET Core Memory Cache Integration**: How to configure `IMemoryCache` with EF Core in dependency injection scenarios
4. **Performance Comparison**: Demonstrating the performance impact of service provider caching

## Key Concepts Demonstrated

### EnableServiceProviderCaching

- **Default**: `true` (enabled) - Service providers are cached and reused
- **When to disable**: Testing scenarios, compiled models, dynamic configurations
- **Performance impact**: Significant improvement when enabled for multiple context instances

### Memory Cache Integration

- EF Core 3.0+ requires explicit `AddMemoryCache()` registration
- `AddEntityFramework*` methods register `IMemoryCache` with size limits
- Proper configuration prevents unbounded memory growth

## Running the Sample

```bash
dotnet run
```

The sample will output performance comparisons and demonstrate different caching behaviors.

## Related Documentation

- [DbContext Configuration](../../core/dbcontext-configuration/index.md#service-provider-caching)
- [EF Core Performance](../../core/performance/index.md)
- [EF Core 3.0 Breaking Changes](../../core/what-is-new/ef-core-3.x/breaking-changes.md#addentityframework-adds-imemorycache-with-a-size-limit)