首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >不一样的 EF Interceptor 注入

不一样的 EF Interceptor 注入

作者头像
郑子铭
发布2025-03-07 18:02:17
发布2025-03-07 18:02:17
2230
举报

EF 不一样的 Interceptor 注入

Intro

最近在 GitHub 上看到一个 issue 结合 Aspire 使用 EF 时遇到想要从从依赖注入中注册 Interceptor ,之前我们也分享过一次可以使用基于 IServiceProvider 来注册,但是 Aspire 的封装注册方法的时候不支持 IServiceProvider 参数,

导致没有办法从依赖注入中获取 service,于是看了下 EF 的注册实现分享了一个可以 替代的解决方法,如果你也在使用 Aspire 的 EF 扩展也可以试一下这个方式来注册 Interceptor

Implement

由于不能直接使用基于 IServiceProvider 的注册方法来注册了,我们自己来研究下基于 IServiceProvider 的注册过程是什么样的

AddDbContext

AddDbContext2

AddCoreServices

ConfigureDbContext

CreateDbContextOptions

我们可以看到最后注册了一个 IDbContextOptionsConfiguration<TContext> 的服务,类似于 options 模式里的 configure service 一样配置 DbContextOptions 所以我们可以单独注册一个 IDbContextOptionsConfiguration<TContext> 服务来配置 DbContextOptions,给 DbContext 注册 Interceptor,所以我们可以封装一个类似下面这样的方法:

代码语言:javascript
复制
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;
}

通过注册一个 configure service 来从 IServiceProvider 中获取 Interceptor 并配置 DbContext,由于 DbContext 默认的服务声明周期是 Scoped 所以我们也将服务默认注入为 Scoped,为了支持用户自定义 DbContext 服务生命周期我们也支持下自定义服务声明周期

这样定义之后会发现有一个 warning,如下图所示

因为这个 DbContextOptionsConfiguration 是 EF 内部的一个类型,虽然这个类似是 public 但是,不建议直接使用,后续版本可能会发生破坏性的变更,我们可以忽略这个警告,或者自己实现一下这个类型,也比较简单,实现如下:

代码语言:javascript
复制
internal sealed class DbContextOptionsConfiguration<TContext>(
    Action<IServiceProvider, DbContextOptionsBuilder> optionsAction
    )
    : IDbContextOptionsConfiguration<TContext>
    whereTContext : DbContext
{
    private readonly Action<IServiceProvider, DbContextOptionsBuilder> _optionsAction = optionsAction ?? throw new ArgumentNullException(nameof(optionsAction));

    public void Configure(IServiceProvider serviceProvider, DbContextOptionsBuilder optionsBuilder)
    {
        _optionsAction.Invoke(serviceProvider, optionsBuilder);
    }
}

再优化下前面的方法,最后方法定义如下:

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

Sample

我们来写一个方法来测试一下,测试代码如下:

代码语言:javascript
复制
var services = new ServiceCollection();
services.AddDbContext<FileTestDbContext>(options =>
{
    options.UseInMemoryDatabase("test");
});
services.AddDbContextInterceptor<FileTestDbContext, SavingInterceptor>();
awaitusingvar provider = services.BuildServiceProvider();
usingvar scope = provider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<FileTestDbContext>();
await dbContext.Database.EnsureCreatedAsync();
dbContext.Entities.Add(new TestEntity { Id = 2, Name = "1" });
await dbContext.SaveChangesAsync();


file sealed class FileTestDbContext(DbContextOptions<FileTestDbContext> options) : DbContext(options)
{
    public DbSet<TestEntity> Entities { get; set; }
}

file sealed class TestEntity
{
    publicint Id { get; set; }
    publicstring Name { get; set; }
}

file sealed class SavingInterceptor : SaveChangesInterceptor
{
    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        Console.WriteLine("SavingChangesAsync");
        returnbase.SavingChangesAsync(eventData, result, cancellationToken);
    }

    public override ValueTask<int> SavedChangesAsync(SaveChangesCompletedEventData eventData, int result,
        CancellationToken cancellationToken = default)
    {
        Console.WriteLine("SavedChangesAsync");
        returnbase.SavedChangesAsync(eventData, result, cancellationToken);
    }
}

我们来验证下,输出结果如下:

代码语言:javascript
复制
SavingChangesAsync
SavedChangesAsync

可以看到我们的 interceptor 有在正常工作了~~

References

  • https://github.com/dotnet/efcore/issues/35411
  • https://github.com/dotnet/aspire/issues/7020
  • https://github.com/dotnet/aspire/blob/be904514949ba5d94e2e4b6d7b8890ab63b88316/src/Components/Aspire.Microsoft.EntityFrameworkCore.SqlServer/AspireSqlServerEFCoreSqlClientExtensions.cs#L37
  • https://github.com/dotnet/efcore/blob/d96dde2fd314154689d5f5253b18eb3e7ee55711/src/EFCore/Extensions/EntityFrameworkServiceCollectionExtensions.cs#L1137
  • https://github.com/WeihanLi/WeihanLi.EntityFramework/blob/015fa8de997add46f463fcb67fa9f5a9ada4b814/src/WeihanLi.EntityFramework/DbContextOptionsConfiguration.cs
  • https://github.com/WeihanLi/WeihanLi.EntityFramework/blob/dev/src/WeihanLi.EntityFramework/EFExtensions.cs#L221-L237
  • https://github.com/WeihanLi/WeihanLi.EntityFramework/blob/015fa8de997add46f463fcb67fa9f5a9ada4b814/samples/WeihanLi.EntityFramework.Sample/Program.cs#L438-L454
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-03-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet NB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • EF 不一样的 Interceptor 注入
    • Intro
    • Implement
    • Sample
    • References
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档