efcore
efcore copied to clipboard
Add API to remove provider configuration.
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);
}
}
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...
Not sure how or why, but removing the IDbContextOptionsConfiguration<ApplicationDbContext> did the trick:
var dbContextDescriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));
@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?
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.
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.
@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.
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.
@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.
Amazing, thanks @loekensgard ! I didn't get a chance to put a sample project together yesterday.
Just run into this trying to update from 8 to 9: 8 works, 9 doesn't. So I guess we're staying with 8.
Note for team: I am able to reproduce this and I am investigating. It looks like a regression in ASP.NET testing.
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)
@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.
I usually remove the current registered service:
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<WfEksternDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
@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>));
@ErikEJ Yeah, I saw that in one of the repros--doesn't help in 9.
Seeing the same issue, tagging myself in for updates.
Seeing same issue, tagging myself as well.
oh hai. Yep, updating my Pluralsight course demos to .NET 9 and ran into this. Will keep eyes on it! Thanks for everyone's contributions to the issue. (Update: my custom web factory has always removed both typeof(DbContextOptions<PubContext>) and typeof(PubContext) )
I'm seeing this as well - just pitching in. Removing the IDbContextOptionsConfiguration<ApplicationDbContext> did remove the error, but as mentioned it's probably not the way to fix it.
I was comparing debugging the .net 8 versin and .net 9. The effect of removing the dbcontext and dbcontextoptions on the services is the same. However, when adding in my two sqlite services, there is a difference. Here is the code for adding in SQLite:
// Create open SqliteConnection so EF won't automatically close it.
services.AddSingleton<DbConnection>(container =>
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
return connection;
});
services.AddDbContext<PubContext>((container, options) =>
{
var connection = container.GetRequiredService<DbConnection>();
options.UseSqlite(connection);
});
What's extra in EF9 is the IDbContextOptionsConfiguration (first highlighted row):
Also I noticed that after my code creawtes the customwebapplicationfactory which does the above re-org and then calls GetRequiredSErvice, it's hitting the adddbcontext in program again...the one I just removed.
using (var scope = appFactory.Services.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<PubContext>();
db.Database.EnsureCreated();
}
and the failure happens when calling EnsureCreated.
That appFactory object is the instance of the customwebApplicationFactory and it's CreateScope method is where I'm removing the original services (for sqlserver) and adding in the new one (for SqlIte)
Not sure if this is useful but there it is....
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.
Unfortunately the Microsoft staff said this different error message would be tracked here: https://github.com/dotnet/efcore/issues/35179 And closed that issue.
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.
@rwb196884 Yes. That is what I was trying to communicate.
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!
I have the same problem, for now I will have to stay on .NET8. I tag myself too.
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 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?"
@BlNARYFOUR Thanks for pointing this out. Filed https://github.com/dotnet/AspNetCore.Docs/issues/34584
Once the specialized API is added https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests#customize-webapplicationfactory should be updated
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