dotnet / docs-aspire

This repository contains .NET Aspire documentation.
https://learn.microsoft.com/dotnet/aspire
MIT License
83 stars 111 forks source link

'OnConfiguring' cannot be used to modify DbContextOptions when DbContext pooling is enabled #1601

Open dradovic opened 2 months ago

dradovic commented 2 months ago

Is there an existing issue for this?

Describe the bug

I'm trying to migrate a working ASP.NET Core app to Aspire .NET. I've added SQL Server support in the following way:

  1. In the AppHost project, I've added <PackageReference Include="Aspire.Hosting.SqlServer" Version="8.2.0" /> and a bit of configuration:
    var sql = builder.AddSqlServer("sql");
    var sqldb = sql.AddDatabase("sqldb");
    ...
    builder.AddProject...
    .WithReference(sqldb)...
  2. In the "project" itself, I've added <PackageReference Include="Aspire.Microsoft.EntityFrameworkCore.SqlServer" Version="8.2.0" /> and I have:
    var builder = WebApplication.CreateBuilder(args);
    builder.AddServiceDefaults();
    builder.AddSqlServerDbContext<ApplicationDbContext>("sqldb"); // this is new in order to...
    //builder.Services.AddDbContext<ApplicationDbContext>(); // ...replace this
    ...

On the first usage (injection) of ApplicationDbContext, I get:

System.InvalidOperationException
  HResult=0x80131509
  Message='OnConfiguring' cannot be used to modify DbContextOptions when DbContext pooling is enabled.
  Source=Microsoft.EntityFrameworkCore
  StackTrace:
   at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.Set[TEntity]()
   ...

The existing app never used the DbContext pooling feature, so I tried to disable it by adding:

"Aspire": {
    "Microsoft": {
        "EntityFrameworkCore": {
            "SqlServer": {
                "ConnectionString": "Server=localhost;Database=...;Trusted_Connection=True;MultipleActiveResultSets=True;Encrypt=False",
                "DbContextPooling": false,
                "DisableHealthChecks": true,
                "DisableTracing": false,
                "DisableMetrics": false
            }
        }
    }
},

to the AppHost's appsettings.json. However, the exception is still raised. Which makes me wonder if the "DbContextPooling": false is respected. Or should I put this configuration somewhere else? (I've tried my luck into add it to the project's appsettings but that didn't change anything either.)

Expected Behavior

With "DbContextPooling": false configured, I would expect that I don't get an error that relates to DbContext pooling.

Steps To Reproduce

No response

Exceptions (if any)

System.InvalidOperationException HResult=0x80131509 Message='OnConfiguring' cannot be used to modify DbContextOptions when DbContext pooling is enabled.

.NET Version info

.NET SDK: Version: 8.0.401 Commit: 811edcc344 Workload version: 8.0.400-manifests.57f7c351 MSBuild version: 17.11.4+37eb419ad

Runtime Environment: OS Name: Windows OS Version: 10.0.19045 OS Platform: Windows RID: win-x64 Base Path: C:\Program Files\dotnet\sdk\8.0.401\

.NET workloads installed: Configured to use loose manifests when installing new manifests. [aspire] Installation Source: SDK 8.0.400, VS 17.11.35219.272 Manifest Version: 8.2.0/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.aspire\8.2.0\WorkloadManifest.json Install Type: Msi

Host: Version: 8.0.8 Architecture: x64 Commit: 08338fcaa5

.NET SDKs installed: 5.0.408 [C:\Program Files\dotnet\sdk] 6.0.100 [C:\Program Files\dotnet\sdk] 6.0.133 [C:\Program Files\dotnet\sdk] 6.0.425 [C:\Program Files\dotnet\sdk] 7.0.120 [C:\Program Files\dotnet\sdk] 7.0.203 [C:\Program Files\dotnet\sdk] 7.0.410 [C:\Program Files\dotnet\sdk] 8.0.108 [C:\Program Files\dotnet\sdk] 8.0.400 [C:\Program Files\dotnet\sdk] 8.0.401 [C:\Program Files\dotnet\sdk]

.NET runtimes installed: Microsoft.AspNetCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.33 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.33 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 6.0.33 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 8.0.8 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found: x86 [C:\Program Files (x86)\dotnet] registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables: Not set

Anything else?

I'm developing using Visual Studio Professional 17.11.1.

sliekens commented 2 months ago

Have you tried using a key that matches your DbContext name?

{
  "Aspire": {
    "Microsoft": {
      "EntityFrameworkCore": {
        "ApplicationDbContext": { <-- match DbContext name
          "ConnectionString": "YOUR_CONNECTIONSTRING",
          "DbContextPooling": false,
          "DisableHealthChecks": true,
          "DisableTracing": true,
          "DisableMetrics": false
        }
      }
    }
  }
}
dradovic commented 2 months ago

Based on your suggestion, I started playing around with configuration using appsettings and currently, I'm not managing to get Aspire to pick up any configuration 🤔 I'll play around more with it and report back. I'm pretty sure that I'm doing something wrong here - I just need to figure out what.

dradovic commented 2 months ago

I've created a small repro repository: https://github.com/dradovic/repro-aspire-dbcontext

Playing around with it, I've realized that:

  1. In order for a DbContext to receive the connection string that points to the SQL Server container that is created by Aspire, I must call the constructor of the DbContext with the provided options: public BloggingContext(DbContextOptions options) : base(options) {...}. It took me quite a while to figure this out as it's not mentioned anywhere on https://learn.microsoft.com/en-us/dotnet/aspire/database/sql-server-entity-framework-integration. The docs should be improved there, IMHO.
  2. In order to use a different (already running) SQL Server instance (for example a local Windows SQL Server), I have to configure the connection string in the project and not in the AppHost. This is not what I want because it means that Aspire will still spin up a SQL Server container although it's not being used. So the question is how to configure the connection string in the AppHost so that it can be shared with all projects and that Aspire doesn't spin up a SQL Server container on its own. I suspect this can somehow be done using External Parameters but that's an investigation of its own that I haven't done yet (hints appreciated).
  3. And coming back to the DbContext pooling problem (which this issue is about): even in the project's appsettings I wasn't able to use the configuration provider approach. For some reasons the settings are simply not being picked up. In my repro, I've added comment there: <-- why is this ignored? on https://github.com/dradovic/repro-aspire-dbcontext/blob/main/AspireDbContext/appsettings.json. So obviously "DbContextPooling": false, will neither be picked up. I've tried with SqlServer, BloggingContext, SqlServer:BloggingContext and sqldb as a configuration section key, but none of these worked.
  4. The MicrosoftEntityFrameworkCoreSqlServerSettings used in the inlined configuration approach don't contain a DisableDbContextPooling property. I guess, it would make sense to add this to be consistent with what can be configured by other means.

So TL;DR: the question remains how to disable DbContext pooling.

maddymontaquila commented 2 months ago

If you don't want to do pooling, you should be using the Enrich api https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.aspiresqlserverefcoresqlclientextensions.enrichsqlserverdbcontext?view=dotnet-aspire-8.0 - regardless we should put this in the docs somewhere

maddymontaquila commented 2 months ago

Moving to docs but also we should add some XML info too

dradovic commented 2 months ago

@maddymontaquila Thanks for the suggestion. However, EnrichSqlServerDbContext uses the same MicrosoftEntityFrameworkCoreSqlServerSettings that I already mentioned above in point 4. It has DisableRetry, DisableTracing but I don't see anything pooling related.

davidfowl commented 2 months ago

Enrich doesn't enable pooling, you are supposed to use the Enrich* method with the normal Entity Framework AddDbContext method (where you get full control over the configuration). i.e.

Delete this:

builder.AddSqlServerDbContext<ApplicationDbContext>("sqldb"); // this is new in order to...

Replace it with:

builder.Services.AddDbContext<ApplicationDbContext>(...);
builder.EnrichSqlServerDbContext<ApplicationDbContext>();
dradovic commented 2 months ago

@davidfowl alright! Makes sense. So one uses the normal EF AddDbContext to configure everything as usual (including interceptors which would answer https://github.com/dotnet/aspire/issues/3069) and then with EnrichSqlServerDbContext we have the chance to configure how Aspire interacts with the EF DbContext.