-
Notifications
You must be signed in to change notification settings - Fork 229
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
Mapping enum issues with multiple DbContext's #3375
Comments
Can you please submit a minimal, runnable code sample? Partial snippets are very rarely enough to understand exactly what a user is doing. |
Here ya go, simple as that. using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
const string connectionString = "Host=localhost;Port=5432;Database=root;Username=root;Password=root"; // This doesnt need to be up, just needs to be a valid conn string
builder.Services.AddDbContextPool<MyContext>(optionsBuilder =>
{
optionsBuilder.UseNpgsql(connectionString, npgsql =>
{
npgsql.MapEnum<FunnyEnum>(); // Map Enum
});
});
builder.Services.AddPooledDbContextFactory<MyContext>(optionsBuilder =>
{
optionsBuilder.UseNpgsql(connectionString, npgsql =>
{
npgsql.MapEnum<FunnyEnum>(); // Map Enum, but this seems to cause the issue
});
});
var app = builder.Build();
await using var scope = app.Services.CreateAsyncScope();
var myContext = scope.ServiceProvider.GetRequiredService<MyContext>(); // Request context to initialize
var tryToQuery = await myContext.Test.FirstOrDefaultAsync(); // This will throw an exception
app.Run();
public class MyContext : DbContext
{
public MyContext() { }
public MyContext(DbContextOptions<MyContext> options) : base(options) { }
public virtual DbSet<TestSet> Test { get; set; }
}
public class TestSet
{
public Guid Id { get; set; }
public FunnyEnum FunnyEnum { get; set; }
}
public enum FunnyEnum
{
Yes
} csproj is just web sdk and npgsql efcore <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
</ItemGroup>
</Project> |
Facing the same issue, it seems like the MapEnum options write to a options.EnumDefinitions In my config I'm reusing the same exact npgsql options - I get the same one via a method
Sadly the old way to do enums don't seems to work anymore and this cause issues with the new way. Is there anyway to load the config somehow outside of the dbcontext adds ? something like
|
Thanks for the minimal repro @LucHeart - I can see the problem happening. So the way EF configuration works, if you specify both AddDbContextPool() and AddPooledDbContextFactory(), the configuration lambdas in both calls incrementally configure the same options; the context instances you get from both injection methods (direct injection, injection of the context factory) have the same options and behave identically. This means that your code is effectively the same as duplicate invocation of MapEnum, i.e.: protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql("Host=localhost;Username=test;Password=test", o =>
{
o.MapEnum<FunnyEnum>();
o.MapEnum<FunnyEnum>();
}) I'll think a bit and consult with other EF members to see what we think of this... I could of course add logic to the PG provider that detects duplicate and/or conflicting mapping (keep in mind that the 2nd MapEnum could specify a different store type, or name translator - that would be incompatible and have to throw). But it seems wrong for users to duplicate the context configuration just because the want to use both AddDbContextPool() and AddPooledDbContextFactory(). So at least as a temporary workaround, I'd recommend doing the following: services.AddDbContextPool<MyContext>(optionsBuilder =>
optionsBuilder.UseNpgsql(connectionString, npgsql => npgsql.MapEnum<FunnyEnum>()));
services.AddPooledDbContextFactory<MyContext>(_ => {}); In other words, configure only once - in the first lambda - and do nothing in the second. But as I wrote above, I'll consult with the EF team to understand this better. /cc @ajcvickers Minimal repro without ASP.NET and some changesvar connectionString = "Host=localhost;Username=test;Password=test";
var services = new ServiceCollection();
services.AddDbContextPool<MyContext>(optionsBuilder =>
optionsBuilder.UseNpgsql(connectionString, npgsql => npgsql.MapEnum<FunnyEnum>()));
// services.AddPooledDbContextFactory<MyContext>(optionsBuilder =>
// optionsBuilder.UseNpgsql(connectionString, npgsql
// => npgsql.MapEnum<FunnyEnum>()));
services.AddPooledDbContextFactory<MyContext>(_ => {});
var serviceProvider = services.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var contextNotFromFactory = scope.ServiceProvider.GetRequiredService<MyContext>();
var contextFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<MyContext>>();
using var contextFromFactory = contextFactory.CreateDbContext();
await contextNotFromFactory.Database.EnsureDeletedAsync();
await contextNotFromFactory.Database.EnsureCreatedAsync();
_ = await contextNotFromFactory.Test.FirstOrDefaultAsync();
_ = await contextFromFactory.Test.FirstOrDefaultAsync();
public class MyContext : DbContext
{
public MyContext() { }
public MyContext(DbContextOptions<MyContext> options) : base(options) { }
public virtual DbSet<TestSet> Test { get; set; }
}
public class TestSet
{
public Guid Id { get; set; }
public FunnyEnum FunnyEnum { get; set; }
}
public enum FunnyEnum { Yes } |
From discussion with the EF team: we agree that double configuration is a bad thing (i.e. repeating the same MapEnum twice); we may look into allowing both DbContext and IDbContextFactory to be registered in DI via a single call (dotnet/efcore#26528). Regardless, we think it's a good idea for the PG provider to handle this scenario better, i.e. detect and ignore duplicate MapEnum invocations, and throw (or possibly do last-one-wins) for incompatible invocations. |
I agree..
thats what I've been doing in the meantime, just looks kinda sketchy but works fine |
FWIW I think repeating the same configuration twice is even more sketchy... :/ |
Yeah that too |
When using multiple DbContexts with the same connection string or data source and trying to .MapEnum the enums present on both of them will throw an exception at startup. We ran into this issue while trying to upgrade from net8 to net9
We are using DbContextPool and PooledDbContextFactory in our application, because in same specific parts it makes more sense to use the factory.
The same is true when using a datasource. (It really doesnt make sense to me that .MapEnum on the datasource doesnt do anything btw. not sure if this is intended. I assumed just defining it on the data source was enough to begin with)
The error that is being thrown
The text was updated successfully, but these errors were encountered: