Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions entity-framework/core/dbcontext-configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,95 @@ The final result is an `ApplicationDbContext` instance created for each request

Read further in this article to learn more about configuration options. See [Dependency injection in ASP.NET Core](/aspnet/core/fundamentals/dependency-injection) for more information.

## Multiple AddDbContext calls and configuration precedence

Starting with EF Core 8.0, when multiple `AddDbContext` calls are made with the same context type, the **last call takes precedence** over earlier calls. This change was made to provide consistent configuration behavior and allow for better configuration override scenarios.

> [!IMPORTANT]
> **Breaking change in EF Core 8.0**: In EF Core 7.0 and earlier, the first `AddDbContext` call would take precedence. Starting with EF Core 8.0, the last call takes precedence. See [breaking changes in EF Core 8.0](xref:core/what-is-new/ef-core-8.0/breaking-changes#AddDbContext) for more information and migration guidance.

### When multiple AddDbContext calls occur

Multiple `AddDbContext` calls for the same context type can happen in several scenarios:

- **Library and application integration**: A library provides default configuration, and the application overrides it
- **Modular applications**: Different modules or components configure the same context
- **Environment-specific configuration**: Different configurations are applied based on conditions
- **Testing scenarios**: Test-specific configurations override application defaults

### Basic usage with multiple AddDbContext calls

When you register the same `DbContext` type multiple times, the final registration will be used:

<!--
var services = new ServiceCollection();

// First configuration - will be overridden in EF Core 8.0
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("FirstDatabase"));

// Second configuration - this takes precedence (EF Core 8.0 behavior)
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("SecondDatabase")
.EnableSensitiveDataLogging());

var serviceProvider = services.BuildServiceProvider();
-->
[!code-csharp[MultipleAddDbContextCalls](../../../samples/core/Miscellaneous/ConfiguringDbContext/ConfigureDbContextSample/Program.cs?name=MultipleAddDbContextCalls)]

### Configuration precedence scenarios

This change is particularly useful in scenarios where:

- Libraries provide default `DbContext` configuration
- Applications need to override library defaults
- Different modules or components need to modify the same context configuration

For example, a library might provide a default configuration:

```csharp
// Library's default configuration
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(defaultConnectionString));
```

And the application can then override it:

```csharp
// Application's override - this will be used in EF Core 8.0
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(customConnectionString)
.EnableSensitiveDataLogging());
```

### Alternative approaches for conditional configuration

If you need conditional configuration based on runtime conditions, consider using a factory pattern or options configuration:

<!--
var services = new ServiceCollection();

// Mock environment and configuration services
var isDevelopment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";

services.AddDbContext<BlogContext>((serviceProvider, options) =>
{
if (isDevelopment)
{
options.UseInMemoryDatabase("DevelopmentDatabase")
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
else
{
options.UseInMemoryDatabase("ProductionDatabase");
}
});

var serviceProvider = services.BuildServiceProvider();
-->
[!code-csharp[ConditionalConfiguration](../../../samples/core/Miscellaneous/ConfiguringDbContext/ConfigureDbContextSample/Program.cs?name=ConditionalConfiguration)]

<!-- See also [Using Dependency Injection](TODO) for advanced dependency injection configuration with EF Core. -->

## Basic DbContext initialization with 'new'
Expand Down
26 changes: 22 additions & 4 deletions entity-framework/core/what-is-new/ef-core-8.0/breaking-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -627,19 +627,37 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)

#### Old behavior

Previously, when multiple calls to `AddDbContext`, `AddDbContextPool`, `AddDbContextFactory` or `AddPooledDbContextFactor` were made with the same context type but conflicting configuration, the first one won.
Previously, when multiple calls to `AddDbContext`, `AddDbContextPool`, `AddDbContextFactory` or `AddPooledDbContextFactory` were made with the same context type but conflicting configuration, the first one won.

For example:

```csharp
services.AddDbContext<BlogContext>(options => options.UseSqlServer(connectionString1));
services.AddDbContext<BlogContext>(options => options.UseSqlite(connectionString2)); // This was ignored

// The context would use SQL Server (connectionString1)
```

#### New behavior

Starting with EF Core 8.0, the configuration from the last call one will take precedence.
Starting with EF Core 8.0, the configuration from the last call will take precedence.

Using the same example:

```csharp
services.AddDbContext<BlogContext>(options => options.UseSqlServer(connectionString1)); // This is now ignored
services.AddDbContext<BlogContext>(options => options.UseSqlite(connectionString2));

// The context will now use SQLite (connectionString2)
```

#### Why

This was changed to be consistent with the new method `ConfigureDbContext` that can be used to add configuration either before or after the `Add*` methods.
This was changed to provide consistent configuration behavior and support scenarios where multiple components need to configure the same `DbContext` type, with later configuration taking precedence over earlier ones.

#### Mitigations

Reverse the order of `Add*` calls.
Reverse the order of `Add*` calls if you were depending on the previous behavior.

<a name="attributeConventionBase"></a>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ConfigureDbContextSample;

#region BlogContext
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 required string Title { get; set; }
public string? Content { get; set; }
}
#endregion

public class Program
{
public static void Main(string[] args)
{
BasicAddDbContextExample();
MultipleAddDbContextCallsExample();
ConditionalConfigurationExample();
}

private static void BasicAddDbContextExample()
{
Console.WriteLine("=== Basic AddDbContext Example ===");

#region BasicAddDbContext
var services = new ServiceCollection();

// DbContext registration with configuration
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("BasicExample")
.EnableSensitiveDataLogging()
.EnableDetailedErrors());

var serviceProvider = services.BuildServiceProvider();
#endregion

using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<BlogContext>();

Console.WriteLine($"Context configured successfully with provider: {context.Database.ProviderName}");
Console.WriteLine();
}

private static void MultipleAddDbContextCallsExample()
{
Console.WriteLine("=== Multiple AddDbContext Calls Example (EF Core 8.0 Behavior) ===");

#region MultipleAddDbContextCalls
var services = new ServiceCollection();

// First configuration - will be overridden in EF Core 8.0
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("FirstDatabase"));

// Second configuration - this takes precedence (EF Core 8.0 behavior)
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("SecondDatabase")
.EnableSensitiveDataLogging());

var serviceProvider = services.BuildServiceProvider();
#endregion

using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<BlogContext>();

Console.WriteLine($"Provider: {context.Database.ProviderName}");
Console.WriteLine("Note: In EF Core 8.0, the second AddDbContext call takes precedence over the first one.");
Console.WriteLine("This is different from EF Core 7.0 and earlier where the first call would win.");
Console.WriteLine();
}

private static void ConditionalConfigurationExample()
{
Console.WriteLine("=== Conditional Configuration Example ===");

#region ConditionalConfiguration
var services = new ServiceCollection();

// Mock environment and configuration services
var isDevelopment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";

services.AddDbContext<BlogContext>((serviceProvider, options) =>
{
if (isDevelopment)
{
options.UseInMemoryDatabase("DevelopmentDatabase")
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
else
{
options.UseInMemoryDatabase("ProductionDatabase");
}
});

var serviceProvider = services.BuildServiceProvider();
#endregion

using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<BlogContext>();

Console.WriteLine($"Context configured for environment: {(isDevelopment ? "Development" : "Production")}");
Console.WriteLine($"Provider: {context.Database.ProviderName}");
Console.WriteLine();
}
}