Skip to content
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

Why is impossible to add interceptors in a generic way and make the scoped service provider resolve them? #35411

Open
luizfbicalho opened this issue Jan 4, 2025 · 8 comments

Comments

@luizfbicalho
Copy link

luizfbicalho commented Jan 4, 2025

Ask a question

I have this code in an aspire project

        builder.AddSqlServerDbContext<ModelContext>("sqldb-model", configureDbContextOptions: (options) =>
        {
            options.UseLazyLoadingProxies()
                .ReplaceService<ILazyLoader, EnableDisableLazyLoader>()
                .AddInterceptors(
                    provider.GetRequiredService<ValidateSaveChangesInterceptor>(),
                    provider.GetRequiredService<ServiceLoggerSaveChangesInterceptor<ModelContext>>()
                );
        });

see that I don't have the service provider in the method, i already asked the aspire team about it

dotnet/aspire#7020

and I would like to use a code like this

        builder.AddSqlServerDbContext<ModelContext>("sqldb-model", configureDbContextOptions: (options) =>
        {
            options.UseLazyLoadingProxies()
                .ReplaceService<ILazyLoader, EnableDisableLazyLoader>()
                .AddInterceptor<ValidateSaveChangesInterceptor>()
                .AddInterceptor<ServiceLoggerSaveChangesInterceptor<ModelContext>>()
        });

UPDATE

I added this code to my DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
 {
     base.OnConfiguring(optionsBuilder);
     optionsBuilder.AddInterceptors(
         provider.GetRequiredService<ValidateSaveChangesInterceptor>(),
         provider.GetRequiredService<ServiceLoggerSaveChangesInterceptor<ModelContext>>()
     );
 }

and added the IServiceProvider to the constructor, thi have two issues for me, one is that I can't configure outside of the the dbcontext

and the other is that the DbContextHealthCheck somehow is using the root service provider and I got this error

Cannot resolve scoped service 'MC.EntityFramework.Extensions.Interceptors.ValidateSaveChangesInterceptor' from root provider.

   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Model.Models.ModelContext.OnConfiguring(DbContextOptionsBuilder optionsBuilder) in C:\\ProjetosGIT\\Model\\Models\\ModelContext.cs:line 14
   at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
   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.Extensions.Diagnostics.HealthChecks.DbContextHealthCheck`1.CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken)

Include provider and version information

EF Core version: 9
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 9.0

@WeihanLi
Copy link
Contributor

WeihanLi commented Jan 9, 2025

Where is the provider from in the sample code?

When trying to register an interceptor from dependency injection, you could use the AddDbContext override to register a scoped interceptor from the DI

services.AddScoped<ValidateSaveChangesInterceptor>();
services.AddDbContext<ModelContext>((provider, options) =>
{
    options.AddInterceptors(provider.GetRequiredService<ValidateSaveChangesInterceptor>());
    options.UseSqlite(sqlLiteConnectionString);
})

may relate to #33813

@luizfbicalho
Copy link
Author

But if I use .net aspire the method doesn't have the service provider parameter, I just added an issue there asking for that

@WeihanLi
Copy link
Contributor

WeihanLi commented Jan 9, 2025

Oh sorry I missed that

@luizfbicalho
Copy link
Author

Oh sorry I missed that

No problem

I think that the .AddInterceptor() could be a good alternative, I'm not sure internally how would warp this, but I imagine that a Keyed ServiceProvider by the DbContext type could be a good solution

It would also help to add interceptors to all dbcontexts at once

@WeihanLi
Copy link
Contributor

WeihanLi commented Jan 9, 2025

maybe this could help

public static IServiceCollection AddDbContextInterceptor<TContext, TInterceptor>(
    this IServiceCollection services,
    ServiceLifetime optionsLifetime = ServiceLifetime.Scoped
    )
    where TContext : DbContext
    where TInterceptor : IInterceptor
{
    Action<IServiceProvider, DbContextOptionsBuilder> optionsAction = (sp, builder) =>
    {
        builder.AddInterceptors(sp.GetRequiredService<TInterceptor>());
    };
    services.Add(ServiceDescriptor.Describe(typeof(TInterceptor), typeof(TInterceptor), optionsLifetime));
    services.Add(ServiceDescriptor.Describe(typeof(IDbContextOptionsConfiguration<TContext>), _ => 
        new DbContextOptionsConfiguration<TContext>(optionsAction), optionsLifetime));
    return services;
}

@luizfbicalho
Copy link
Author

Thanks a lot, I'll try it.

Any chance of this code becoming part of EF Core?

@WeihanLi
Copy link
Contributor

Any chance of this code becoming part of EF Core?

Would need API review from the team @ajcvickers @roji

@luizfbicalho
Copy link
Author

I tested it now, it's working fine,Ill keep it on my code for now, let's see if @ajcvickers likes it

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

4 participants