efcore/EFCore.NamingConventions

UseInternalServiceProvider

FoxTes opened this issue · 9 comments

FoxTes commented

Using UseInternalServiceProvider() I get incorrect behavior of this library. It just stops working.

UseInternalServiceProvider() is required for the factory when creating the migration. Can you tell me how you can work around this problem?

I have already seen PR on this topic before, but there is no solution there.

public class ApplicationContextFactory : IDesignTimeDbContextFactory<ApplicationContext>, IAsyncDisposable
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly IServiceProvider _serviceProvider;

    public ApplicationContextFactory()
    {
        var builder = WebApplication.CreateBuilder(Array.Empty<string>());
        var connectionString = builder.Configuration.GetConnectionString(nameof(ApplicationContext));
        
        builder.Services
            .AddEntityFrameworkNpgsql()
            .AddEntityFrameworkNamingConventions();
        
        var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
        
        _dataSource = dataSourceBuilder.Build();
        _serviceProvider = builder.Build().Services;
    }

    public ApplicationContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<ApplicationContext>();
        optionsBuilder
            .UseNpgsql(_dataSource)
            .UseSnakeCaseNamingConvention()
            .UseInternalServiceProvider(_serviceProvider);

        return new ApplicationContext(optionsBuilder.Options);
    }
    
    public ValueTask DisposeAsync()=> _dataSource.DisposeAsync();
}
FoxTes commented

@Azmond have you solved this problem?

roji commented

@FoxTes you should in almost all cases not be using UseInternalServiceProvider in a normal app - any particular reason you're doing that?

FoxTes commented

@roji
I use UseInternalServiceProvider() only in IDesignTimeDbContextFactory.

I need to get in the context of IOptions (and maybe other servicies) for the OnModelCreating() method.

In the application itself, DI solves this problem.

Is it possible to solve the problem with migrations and IDesignTimeDbContextFactory?

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.ApplyConfiguration(new WorkoutTypeEntityTypeConfiguration(
        this.GetService<IClockService>(),
        this.GetService<IOptions<TenantOption>>()));
}
    
public class WorkoutTypeEntityTypeConfiguration(
        IClockService clockService,
        IOptions<TenantOption> option)
    : IEntityTypeConfiguration<WorkoutTypeEntity>
{
    public void Configure(EntityTypeBuilder<WorkoutTypeEntity> builder)
    {
        builder
            .HasQueryFilter(e => e.TenantId == option.Value.Id);

        builder
            .HasData(FastEnum
                .GetValues<WorkoutType>()
                .Select(type => new WorkoutTypeEntity
                {
                    Id = type,
                    Description = type.GetEnumMemberValue()!,
                    CreatedDateTime = clockService.LocalNow,
                    TenantId = option.Value.Id
                }));
    }
}
FoxTes commented

Well, I solved the problem not in the most obvious way :)

public class ApplicationContextFactory : IDesignTimeDbContextFactory<ApplicationContext>
{
    private readonly IServiceProvider _serviceProvider;

    public ApplicationContextFactory()
    {
        var builder = WebApplication.CreateBuilder(Array.Empty<string>())
            .AddOptions()
            .AddDbContextFactoryServices();
        
        var dataSourceBuilder = new NpgsqlDataSourceBuilder(
            builder.Configuration.GetConnectionString(nameof(ApplicationContext)));
        dataSourceBuilder.MapEnum<ActionType>();
        dataSourceBuilder.MapEnum<WorkoutType>();
        
        builder.Services.AddDbContext<ApplicationContext>(
            options => options
                .UseNpgsql(dataSourceBuilder.Build(), option => option.UseNodaTime())
                .UseSnakeCaseNamingConvention(), 
            ServiceLifetime.Singleton);
        
        _serviceProvider = builder.Build().Services;
    }

    public ApplicationContext CreateDbContext(string[] args) =>
        _serviceProvider.GetRequiredService<ApplicationContext>();
}
roji commented

@FoxTes great that you could get rid of UseInternalServiceProvider(). If you're simply looking for the design-time context to be the same thing that your application sets up in WebApplication, you maybe don't need to define a IDesignTimeDbContextFactory at all - see the docs.

In any case, I'll go ahead and close this as the problem seems to be solved.

FoxTes commented

ASP.NET Core Web Host or .NET Core Generic Host

In my case, WebApplication is used and therefore this is not suitable for me

roji commented

@FoxTes the docs are probably out of date, and WebApplication should work as well; have you tried and seen it not working?

FoxTes commented

Yeap, it's worked! Thanks for the hint

roji commented

Great!