Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
90 changes: 90 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,96 @@ 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.

<a name="configuredbcontext"></a>

## ConfigureDbContext for configuration composition

Starting with EF Core 9.0, you can use `ConfigureDbContext` to apply additional configuration to a `DbContext` either before or after the `AddDbContext` call. This is particularly useful for composing non-conflicting, non-provider-specific configuration in reusable components or tests.

### Basic ConfigureDbContext usage

`ConfigureDbContext` allows you to add configuration without replacing the entire provider configuration:

```csharp
// In a reusable library or component
services.ConfigureDbContext<ApplicationDbContext>(options =>
options.EnableSensitiveDataLogging()
.EnableDetailedErrors());

// Later, in the application
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
```

Both configurations will be applied - the logging and error settings from `ConfigureDbContext`, and the SQL Server provider from `AddDbContext`.

### Using ConfigureDbContext in reusable components

`ConfigureDbContext` is especially useful when creating reusable components that need to add configuration without knowing or modifying the database provider:

```csharp
// In a testing utility library
public static class TestingExtensions
{
public static IServiceCollection AddTestingConfiguration<TContext>(
this IServiceCollection services)
where TContext : DbContext
{
services.ConfigureDbContext<TContext>(options =>
options.EnableSensitiveDataLogging()
.EnableDetailedErrors()
.LogTo(Console.WriteLine));

return services;
}
}

// In your test
services.AddTestingConfiguration<ApplicationDbContext>();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
```

### Provider-specific configuration without connection strings

When you need to apply provider-specific configuration but don't have the connection string, you can use provider-specific configuration methods like `ConfigureSqlEngine`:

```csharp
// Configure SQL Server-specific options without knowing the connection string
services.ConfigureDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(sqlOptions =>
sqlOptions.EnableRetryOnFailure()
.CommandTimeout(30)));

// Later, add the context with the connection string
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
```

### ConfigureDbContext and AddDbContext precedence

When both `ConfigureDbContext` and `AddDbContext` are used, or when multiple calls to these methods are made, the configuration is applied in the order the methods are called, with later calls taking precedence for conflicting options.

For non-conflicting options (like adding logging, interceptors, or other settings), all configurations are composed together:

```csharp
// First: add logging
services.ConfigureDbContext<ApplicationDbContext>(options =>
options.LogTo(Console.WriteLine));

// Second: add the provider
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));

// Third: add sensitive data logging
services.ConfigureDbContext<ApplicationDbContext>(options =>
options.EnableSensitiveDataLogging());

// Result: All three configurations are applied
```

For conflicting options (like specifying different database providers), the last configuration wins. See [breaking changes in EF Core 8.0](xref:core/what-is-new/ef-core-8.0/breaking-changes#AddDbContext) for more information about this behavior change.

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

## Basic DbContext initialization with 'new'
Expand Down
39 changes: 35 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,50 @@ 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.

#### 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.

#### 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 be consistent with the new method `ConfigureDbContext`, that enables configuration composability for non-conflicting configurations. See [DbContext configuration](xref:core/dbcontext-configuration/index#configuredbcontext) for more information.

#### Mitigations

Reverse the order of `Add*` calls.
If your application depends on the previous behavior where the first registration wins, you have several options:

1. **Reorder your registrations**: Place the registration with the configuration you want to use last:

```csharp
services.AddDbContext<MyContext>(options =>
options.UseSqlServer("connection1")); // This will be ignored now

services.AddDbContext<MyContext>(options =>
options.UseSqlServer("connection2")); // This will be used
```

2. **Remove previous registrations**: If possible, remove the conflicting registration.

3. **Use conditional registration**: Check if the service is already registered before adding:

```csharp
if (!services.Any(d => d.ServiceType == typeof(DbContextOptions<MyContext>)))
{
services.AddDbContext<MyContext>(options =>
options.UseSqlServer("connection"));
}
```

4. **Use the new `ConfigureDbContext` method**: This allows you to configure options without registering the context itself. See [DbContext configuration](xref:core/dbcontext-configuration/index#configuredbcontext) for more information:

```csharp
services.ConfigureDbContext<MyContext>(options =>
options.UseSqlServer("connection"));

services.AddDbContext<MyContext>(); // Register the context without configuration
```

<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="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
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

#region TestingExtensions
public static class TestingExtensions
{
public static IServiceCollection AddTestingConfiguration<TContext>(
this IServiceCollection services)
where TContext : DbContext
{
services.ConfigureDbContext<TContext>(options =>
options.EnableSensitiveDataLogging()
.EnableDetailedErrors()
.LogTo(Console.WriteLine));

return services;
}
}
#endregion

public class Program
{
public static void Main(string[] args)
{
BasicConfigureDbContextExample();
ReusableComponentExample();
ConfigurationCompositionExample();
}

private static void BasicConfigureDbContextExample()
{
Console.WriteLine("=== Basic ConfigureDbContext Example ===");

#region BasicConfigureDbContext
var services = new ServiceCollection();

// Configure non-provider-specific options
services.ConfigureDbContext<BlogContext>(options =>
options.EnableSensitiveDataLogging()
.EnableDetailedErrors());

// Add the context with provider configuration
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("BasicExample"));

var serviceProvider = services.BuildServiceProvider();
#endregion

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

Console.WriteLine($"Context configured with provider: {context.Database.ProviderName}");
Console.WriteLine("ConfigureDbContext was called before AddDbContext");
Console.WriteLine();
}

private static void ReusableComponentExample()
{
Console.WriteLine("=== Reusable Component Example ===");

#region ReusableComponent
var services = new ServiceCollection();

// Use the testing extension method
services.AddTestingConfiguration<BlogContext>();

// Add the context with provider
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("TestDb"));

var serviceProvider = services.BuildServiceProvider();
#endregion

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

Console.WriteLine($"Context configured for testing with provider: {context.Database.ProviderName}");
Console.WriteLine("Testing configuration added via extension method");
Console.WriteLine();
}

private static void ConfigurationCompositionExample()
{
Console.WriteLine("=== Configuration Composition Example ===");

#region ConfigurationComposition
var services = new ServiceCollection();

// First: add logging
services.ConfigureDbContext<BlogContext>(options =>
options.LogTo(Console.WriteLine));

// Second: add the provider
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("CompositionExample"));

// Third: add sensitive data logging
services.ConfigureDbContext<BlogContext>(options =>
options.EnableSensitiveDataLogging());

var serviceProvider = services.BuildServiceProvider();
#endregion

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

Console.WriteLine($"Context configured with provider: {context.Database.ProviderName}");
Console.WriteLine("All three configurations were composed together");
Console.WriteLine();
}
}