Skip to content

Using NpgsqlDataSource in DependencyInjection pollutes other containers #2891

@eerhardt

Description

@eerhardt

Running the following program:

<Project Sdk="Microsoft.NET.Sdk">

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

  <ItemGroup>
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0-rc.1" />
    <PackageReference Include="Npgsql.DependencyInjection" Version="8.0.0-preview.4" />
  </ItemGroup>

</Project>
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

RunTest("Host=localhost;Database=test");
Console.WriteLine("First test passed");

RunTest("Host=myserver;Database=mydata");
Console.WriteLine("Second test passed");

static void RunTest(string connectionString)
{
    var serviceCollection = new ServiceCollection();

    serviceCollection.AddNpgsqlDataSource(connectionString);
    serviceCollection.AddDbContext<TestDbContext>(builder => builder.UseNpgsql(connectionString));

    var sp = serviceCollection.BuildServiceProvider();
    var context = sp.GetRequiredService<TestDbContext>();

    if (context.Database.GetDbConnection().ConnectionString != connectionString)
    {
        throw new Exception($"""
            This is broken.
            Expected: {connectionString}
            Actual:   {context.Database.GetDbConnection().ConnectionString}
            """);
    }
}

public class TestDbContext(DbContextOptions<TestDbContext> options) : DbContext(options) { }

produces an error on the 2nd test run:

First test passed
Unhandled exception. System.Exception: This is broken.
Expected: Host=myserver;Database=mydata
Actual:   Host=localhost;Database=test
   at Program.<<Main>$>g__RunTest|0_0(String connectionString) in C:\Users\eerhardt\source\repos\ConsoleApp100\ConsoleApp100\Program.cs:line 22
   at Program.<Main>$(String[] args) in C:\Users\eerhardt\source\repos\ConsoleApp100\ConsoleApp100\Program.cs:line 7

The connection string is reused across DI containers. As far as I can tell, it is because:

  1. EF uses the DbContextOptions as a cache key in here:
    https://github.com/dotnet/efcore/blob/5299be3bfeab62224f29aae9d4adff510878fcf7/src/EFCore/Internal/ServiceProviderCache.cs#L68-L71
  2. DbContextOptions overrides Equals: https://github.com/dotnet/efcore/blob/5299be3bfeab62224f29aae9d4adff510878fcf7/src/EFCore/DbContextOptions.cs#L142-L147
    So DbContextOptions between 2 different DbContexts equal, and the stuff cached for the first context (like the INpgsqlSingletonOptions) are being reused across different DI containers

cc @roji

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions