Skip to content

Add API to remove provider configuration. #35126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Tealons opened this issue Nov 16, 2024 · 37 comments
Open

Add API to remove provider configuration. #35126

Tealons opened this issue Nov 16, 2024 · 37 comments

Comments

@Tealons
Copy link

Tealons commented Nov 16, 2024

After upgrading to .NET 9 I get the following error on db.Database.EnsureCreated(); in the following code:

using (var scope = sp.CreateScope())
{
    var scopedServices = scope.ServiceProvider;
    var db = scopedServices.GetRequiredService<ApplicationDbContext>();
    var logger = scopedServices
        .GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();

    // Ensure the database is created.
    db.Database.EnsureCreated();

    try
    {
        // Seed the database with test data.
        // Utilities.InitializeDbForTests(db);
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "An error occurred seeding the " +
            "database with test messages. Error: {Message}", ex.Message);
    }
}

Image

The exact error:

System.InvalidOperationException
  HResult=0x80131509
  Message=Services for database providers 'Microsoft.EntityFrameworkCore.SqlServer', 'Microsoft.EntityFrameworkCore.InMemory' have been registered in the service provider. Only a single database provider can be registered in a service provider. If possible, ensure that Entity Framework is managing its service provider by removing the call to 'UseInternalServiceProvider'. Otherwise, consider conditionally registering the database provider, or maintaining one service provider per database provider.
  Source=Microsoft.EntityFrameworkCore
  StackTrace:
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, DbContextOptions contextOptions, DbContext context)
   at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService(IInfrastructure`1 accessor, Type serviceType)
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.get_Dependencies()
   at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.EnsureCreated()
   at SpiderIoT.Test.CustomWebApplicationFactory`1.<>c.<ConfigureWebHost>b__0_0(IServiceCollection services) in C:\Source\SpiderIoT\SpiderIoT.Test\CustomWebApplicationFactory.cs:line 54
   at Microsoft.Extensions.Hosting.HostBuilder.InitializeServiceProvider()
   at Microsoft.Extensions.Hosting.HostBuilder.Build()
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateHost(IHostBuilder builder)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.ConfigureHostBuilder(IHostBuilder hostBuilder)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer()
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.get_Services()
   at SpiderIoT.Test.APITest..ctor(CustomWebApplicationFactory`1 factory) in C:\Source\SpiderIoT\SpiderIoT.Test\APITest.cs:line 36
   at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)

My feeling is that removing the previous ApplicationDbContext is not working properly? This is my code (which was adjusted according to the .NET 9 docs, but is still not working):

 // Remove the app's ApplicationDbContext registration.
 var dbContextDescriptor = services.SingleOrDefault(
   d => d.ServiceType ==
   typeof(DbContextOptions<ApplicationDbContext>));

 services.Remove(dbContextDescriptor);

 var dbConnectionDescriptor = services.SingleOrDefault(
     d => d.ServiceType ==
         typeof(DbConnection));

 services.Remove(dbConnectionDescriptor);

 // Add ApplicationDbContext using an in-memory database for testing.
 services.AddDbContext<ApplicationDbContext>(options =>
 {
     options.UseLazyLoadingProxies();
     options.UseInMemoryDatabase("InMemoryDbForTesting");
 });

After hours of Googling and trying stuff out I'm a bit lost how this should work in .NET 9...

@Tealons
Copy link
Author

Tealons commented Nov 16, 2024

Not sure how or why, but removing the IDbContextOptionsConfiguration<ApplicationDbContext> did the trick:


 var dbContextDescriptor = services.SingleOrDefault(
   d => d.ServiceType ==
   typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));

@cincuranet cincuranet closed this as not planned Won't fix, can't repro, duplicate, stale Nov 18, 2024
@Tealons
Copy link
Author

Tealons commented Nov 18, 2024

@cincuranet: closing this feels a bit wrong. The documentation like: https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-9.0#customize-webapplicationfactory will give the error I described? And is my solution the right way to do this? Or is there a better way?

@kymarti
Copy link

kymarti commented Nov 19, 2024

I'm experiencing this same exact issue. In my case, I'm using .NET 8 but have some EF-related packages -- such as EF core -- that I've updated to v9.0.0. Can this be re-opened, @cincuranet? If not, @Tealons perhaps we should create a new thread for this issue?

I would also like to understand if there's been an intentional change in behavior.

  • If yes, shouldn't the documentation @Tealons linked above be updated?
  • If no, I think we need a bug tracking this.

@cincuranet
Copy link
Contributor

You don't want to mix multiple providers with single DbContext. Manually removing IDbContextOptionsConfiguration works, but you're starting to touch internal infrastructure of EF and the underlying implementation can change in the future.

I guess you're doing testing. It would be way better to have setup for tests that registers (only) your desired provider and setup for production. Using methods like IHostingEnvironment.IsProduction, etc. or #ifdef or similar.

@ajcvickers
Copy link
Contributor

@cincuranet I'm concerned about this one, since there is an indication that this is a regression.

@Tealons @kymarti Can one of you please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

@ajcvickers ajcvickers reopened this Nov 19, 2024
@kymarti
Copy link

kymarti commented Nov 19, 2024

Sure, let me try to put something together @ajcvickers , but if you have time @Tealons feel free to go ahead and put something together as well.

@loekensgard
Copy link

loekensgard commented Nov 20, 2024

@ajcvickers
I managed to reproduce the issue in this project EfCore .net9 Test

If you downgrade all packages to .net 8 the tests will succeed, but with .net 9 this exception occurs.

System.ArgumentException : Could not initialize database ---- System.InvalidOperationException : Services for database providers 'Microsoft.EntityFrameworkCore.SqlServer', 'Microsoft.EntityFrameworkCore.InMemory' have been registered in the service provider. Only a single database provider can be registered in a service provider. If possible, ensure that Entity Framework is managing its service provider by removing the call to 'UseInternalServiceProvider'. Otherwise, consider conditionally registering the database provider, or maintaining one service provider per database provider.

@kymarti
Copy link

kymarti commented Nov 20, 2024

Amazing, thanks @loekensgard ! I didn't get a chance to put a sample project together yesterday.

@rwb196884
Copy link

Just run into this trying to update from 8 to 9: 8 works, 9 doesn't. So I guess we're staying with 8.

@ajcvickers
Copy link
Contributor

Note for team: I am able to reproduce this and I am investigating. It looks like a regression in ASP.NET testing.

@ajcvickers
Copy link
Contributor

Looks like this is a change in the behavior WebApplicationFactory when used in tests. Two projects are needed to repro this: a simple web app with this code:

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
        builder.Services.AddDbContext<ContextOfDoom>(options => options.UseSqlServer());
        builder.Build().Run();
    }
}


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

And then a test project with a test and a WebApplicationFactory:

public class TestWebApplicationFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            services.AddDbContext<ContextOfDoom>((_, context) => context.UseInMemoryDatabase("DB"));
            var serviceProvider = services.BuildServiceProvider();
            using var scope = serviceProvider.CreateScope();
            var context = serviceProvider.GetService<ContextOfDoom>()!;
            _ = context.Model;
        });
    }
}

public class SomeTests : IClassFixture<TestWebApplicationFactory>
{
    public SomeTests(TestWebApplicationFactory factory)
    {
        factory.WithWebHostBuilder(_ => { }).CreateClient();
    }

    [Fact]
    public async Task Get_Test()
    {
    }
}

When running the tests in EF8, the web host resolver is not used, and so that Main method is not run, and only the in-memory database is registered.

When running the tests in EF9, the web host resolver runs and so both the SQL Server and in-memory databases are registered, resulting in:

System.InvalidOperationException
Services for database providers 'Microsoft.EntityFrameworkCore.SqlServer', 'Microsoft.EntityFrameworkCore.InMemory' have been registered in the service provider. Only a single database provider can be registered in a service provider. If possible, ensure that Entity Framework is managing its service provider by removing the call to 'UseInternalServiceProvider'. Otherwise, consider conditionally registering the database provider, or maintaining one service provider per database provider.
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, DbContextOptions contextOptions, DbContext context)
   at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
   at Microsoft.EntityFrameworkCore.DbContext.get_Model()
   at TestWebApplicationFactory.<>c.<ConfigureWebHost>b__0_0(IServiceCollection services) in D:\code\repros\WebApplication1\WebApplication1\WebApplication1.Tests\SomeTests.cs:line 18
   at Microsoft.Extensions.Hosting.HostApplicationBuilder.HostBuilderAdapter.ApplyChanges()
   at Microsoft.Extensions.Hosting.HostApplicationBuilder.Build()
   at Microsoft.AspNetCore.Builder.WebApplicationBuilder.Build()
   at Program.Main(String[] args) in D:\code\repros\WebApplication1\WebApplication1\WebApplication1\Program.cs:line 9
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
--- End of stack trace from previous location ---
   at Microsoft.Extensions.Hosting.HostFactoryResolver.HostingListener.CreateHost()
   at Microsoft.Extensions.Hosting.HostFactoryResolver.<>c__DisplayClass10_0.<ResolveHostFactory>b__0(String[] args)
   at Microsoft.AspNetCore.Mvc.Testing.DeferredHostBuilder.Build()
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateHost(IHostBuilder builder)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.DelegatedWebApplicationFactory.CreateHost(IHostBuilder builder)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.ConfigureHostBuilder(IHostBuilder hostBuilder)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer()
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(Uri baseAddress, DelegatingHandler[] handlers)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient(WebApplicationFactoryClientOptions options)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient()
   at SomeTests..ctor(TestWebApplicationFactory factory) in D:\code\repros\WebApplication1\WebApplication1\WebApplication1.Tests\SomeTests.cs:line 27
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)

@ajcvickers
Copy link
Contributor

@AndriySvyryd Assigning to you since you did some work in this area in EF8/9. Root cause could be an external breaking change in the WebApplicationFactory, or it could be something we are doing different.

@ErikEJ
Copy link
Contributor

ErikEJ commented Nov 26, 2024

I usually remove the current registered service:

var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<WfEksternDbContext>));

if (descriptor != null)
{
    services.Remove(descriptor);
}

@loekensgard
Copy link

@ErikEJ, it appears that this issue persists even after removing the currently registered service. In my test project, I have the same configuration, but the error still occurs when running .NET 9.

However @Tealons found a workaround, but this could potentially lead to unexpected behavior?

Not sure how or why, but removing the IDbContextOptionsConfiguration<ApplicationDbContext> did the trick:


 var dbContextDescriptor = services.SingleOrDefault(
   d => d.ServiceType ==
   typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));

@ajcvickers
Copy link
Contributor

@ErikEJ Yeah, I saw that in one of the repros--doesn't help in 9.

@jaliyaudagedara
Copy link
Contributor

Seeing the same issue, tagging myself in for updates.

@ajcvickers ajcvickers assigned ajcvickers and unassigned AndriySvyryd Dec 3, 2024
@JYasgarYTGI
Copy link

JYasgarYTGI commented Dec 30, 2024

I ran into the same issue after upgrading from EF8 to 9. My Startup. cs has four DI DBContexts, so I made sure to remove all four, even though my tests only use three of them.
I also changed the lines in my CustomWebApplicationFactory from:

    ServiceDescriptor descriptor1 = services.SingleOrDefault(
                    d => d.ServiceType == typeof(DbContextOptions<RVSAccountContext>))!;

to

     ServiceDescriptor descriptor1 = services.SingleOrDefault(
                    d => d.ServiceType == typeof(IDbContextOptionsConfiguration<RVSAccountContext>))!;

My tests are running again.

@ericwood8
Copy link

ericwood8 commented Dec 30, 2024

Unfortunately the Microsoft staff said this different error message would be tracked here: #35179 And closed that issue.

@rwb196884
Copy link

Unfortunately the Microsoft staff said this different error message would be tracked here: #35179 And closed that issue.

I think you've got mixed up: on thread #35179 @ajcvickers says to come to this thread.

@ericwood8
Copy link

@rwb196884 Yes. That is what I was trying to communicate.

@julielerman
Copy link

julielerman commented Dec 30, 2024

Yes, @JYasgarYTGI nailed it (my tests pass now, too). But I only have a few basic tests and am wondering if we need to be concerned about side effects of using the IDbContextOptionsConfiguration interface over the DbContextOptions implementation. And it aligns with my discovery that IDbCOntextOptionsConfiguration is "left over" otherwise (as per my testing above). Thanks Jack!

@pampua84
Copy link

pampua84 commented Jan 7, 2025

I have the same problem, for now I will have to stay on .NET8. I tag myself too.

@AndriySvyryd
Copy link
Member

Sorry that it took so long for the response. This is by design.

Starting with 9 DbContextOptions<> in the service provider doesn't contain any configuration itself, instead it uses all the registered IDbContextOptionsConfiguration<> instances to build the configuration. This was done to enable options composition, so that a context can be configured without registering it, this is useful for Aspire as it applies some default configuration.

The best workaround currently is to remove IDbContextOptionsConfiguration<> as shown above. I'll add this to breaking changes.

Since there isn't an API to remove a provider configuration, we can use this issue to track adding it. This is a fairly advanced scenario, so it doesn't need to be easily discoverable. I propose the following:

services.ConfigureDbContext<TestContext>(options =>
    ((IDbContextOptionsBuilderInfrastructure)options).RemoveExtension<SqlServerOptionsExtension>());

@AndriySvyryd AndriySvyryd changed the title InMemory database context with CustomWebApplicationFactory is giving double database context registration error. How do I implement this correctly? Add API to remove provider configuration. Jan 7, 2025
@AndriySvyryd AndriySvyryd added this to the Backlog milestone Jan 7, 2025
@BlNARYFOUR
Copy link

@AndriySvyryd if it is by design, surely this should be edited in the documentation of learn.microsoft .NET 9 ASAP?

Anyone currently following the documentation, will hit a very unpleasant surprise "I did exactly as the docs said... WHY DOESN'T IT WORK?"

@AndriySvyryd
Copy link
Member

AndriySvyryd commented Jan 28, 2025

@BlNARYFOUR Thanks for pointing this out. Filed dotnet/AspNetCore.Docs#34584

Once the specialized API is added https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests#customize-webapplicationfactory should be updated

@dadaPipes
Copy link

I could not figure out how to solve it from the comments, but this blog post helped med out.
In case anyone stumples upon this thread and is as confused as I was.

https://www.devgem.io/posts/resolving-multiple-ef-core-database-providers-in-asp-net-core-integration-testing

@JYasgarYTGI
Copy link

Taking my post above, I wrote it up too.

https://jackyasgar.net/solved-ef-8-to-9-migration-database-provider-exception/

@Tealons
Copy link
Author

Tealons commented Feb 8, 2025

I didn't know that link building was still a thing :)

@adcorduneanu
Copy link

When this is a breaking change, version 9 of EF Core should be limited to .net 9 - we have applications on LTS (.net 8) failing because of this.

@AndriySvyryd
Copy link
Member

@adcorduneanu You can use EF Core 8 on .net 8

@adcorduneanu
Copy link

@AndriySvyryd, this is not an option when you have over 150 projects in the field, and the updates are done automatically.

If this was targeting .net 9 then should be only on .net 9.

@rwb196884
Copy link

Well that gets the tests to run but they're failing.

Seems that navigation properties aren't being saved.

        public async Task Get_One()
        {
            // Arrange
            _configurationDbContext.IdentityResources.Add(new E.IdentityResource()
            {
                Name = "foo",
                Properties = new List<E.IdentityResourceProperty>() { 
                    new E.IdentityResourceProperty() { Key = "One", Value = "One"},
                    new E.IdentityResourceProperty() { Key = "Two", Value = "Two"},
                    new E.IdentityResourceProperty() { Key = "three", Value = "three"}
                }
            });
            _configurationDbContext.SaveChanges();

            // Act
            HttpResponseMessage? response = await _client.GetAsync("api/ServerAdmin/IdentityResources");

            // Assert
            string content = await response.Content.ReadAsStringAsync();
            IEnumerable<M.IdentityResource> result = DeserializeJson<List<M.IdentityResource>>(content);

            response.EnsureSuccessStatusCode();
            Assert.Single(result);
            Assert.Equal("foo", result.First().Name);

            Assert.Equal(3, result.First().Properties.Count); // fails; .Properties is empty.

@rwb196884
Copy link

I'll just comment it out. Unit tests are worthless when you have to spend more time fucking about to fix them then on real code.

nlivaic added a commit to nlivaic/TestTemplate15 that referenced this issue Mar 29, 2025
nlivaic added a commit to nlivaic/SparkRoseDigital.Template that referenced this issue Apr 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests