不一样的 EF Interceptor 注入

360影视 2025-01-14 09:51 2

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

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

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

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

我们可以看到最后注册了一个

IDbContextOptionsConfiguration的服务,类似于 options 模式里的 configure service 一样配置 DbContextOptions 所以我们可以单独注册一个服务来配置 DbContextOptions,给 DbContext 注册 Interceptor,所以我们可以封装一个类似下面这样的方法:publicstaticIServiceCollection AddDbContextInterceptor(
thisIServiceCollection services,
ServiceLifetime optionsLifetime = ServiceLifetime.Scoped
)
whereTContext : DbContext
whereTInterceptor : IInterceptor
{
Action optionsAction = (sp, builder) =>
{
builder.AddInterceptors(sp.GetRequiredService);
};
services.Add(ServiceDescriptor.Describe(typeof(TInterceptor),typeof(TInterceptor), optionsLifetime));
typeof(IDbContextOptionsConfiguration), _ =>
newDbContextOptionsConfiguration(optionsAction), optionsLifetime));
returnservices;
}
通过注册一个 configure service 来从中获取 Interceptor 并配置 DbContext,由于 DbContext 默认的服务声明周期是Scoped所以我们也将服务默认注入为Scoped,为了支持用户自定义 DbContext 服务生命周期我们也支持下自定义服务声明周期

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

因为这个DbContextOptionsConfiguration是 EF 内部的一个类型,虽然这个类似是 public 但是,不建议直接使用,后续版本可能会发生破坏性的变更,我们可以忽略这个警告,或者自己实现一下这个类型,也比较简单,实现如下:internalsealed class DbContextOptionsConfigurationTContext>(
ActionIServiceProviderDbContextOptionsBuilderoptionsAction
)
:IDbContextOptionsConfigurationTContext
whereTContext:DbContext
{
private readonlyAction _optionsAction = optionsAction ??throw newArgumentException(nameof(optionsAction));

publicvoidConfigure(IServiceProvider serviceProvider, DbContextOptionsBuilder optionsBuilder)
{
_optionsAction.Invoke(serviceProvider, optionsBuilder);
}
}

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

publicstaticIServiceCollection AddDbContextInterceptor(
thisIServiceCollection services,
ServiceLifetime optionsLifetime = ServiceLifetime.Scoped
)
whereTContext : DbContext
whereTInterceptor : IInterceptor
{
ArgumentException.ThrowIf(services);
Action optionsAction = (sp, builder) =>
{
builder.AddInterceptors(sp.GetRequiredService);
};
services.TryAdd(ServiceDescriptor.Describe(typeof(TInterceptor),typeof(TInterceptor), optionsLifetime));
services.Add(ServiceDescriptor.Describe(typeof(IDbContextOptionsConfiguration),
_ =>newDbContextOptionsConfiguration(optionsAction), optionsLifetime));
returnservices;
}
Sample

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

varservices =newServiceCollection;
services.AddDbContext(options =>
{
options.UseInMemoryDatabase("test");
});
services.AddDbContextInterceptor;
awaitusingvarprovider = services.BuildServiceProvider;
usingvarscope = provider.CreateScope;
vardbContext = scope.ServiceProvider.GetRequiredService;
awaitdbContext.Database.EnsureCreatedAsync;
dbContext.Entities.Add(newTestEntity { Id =2, Name ="1"});
awaitdbContext.SaveChangesAsync;

filesealedclassFileTestDbContext(DbContextOptions options) :DbContext(options)
{
publicDbSet Entities {get;set; }
}

filesealed class TestEntity
{
publicintId {get;set; }
publicstringName {get;set; }
}

filesealed class SavingInterceptor:SaveChangesInterceptor
{
public overrideValueTaskint>> SavingChangesAsync(DbContextEventData eventData, InterceptionResultint> result,
CancellationToken cancellationToken =default)
{
Console.WriteLine("SavingChangesAsync");
returnbase.SavingChangesAsync(eventData, result, cancellationToken);
}

publicoverrideValueTaskintSavedChangesAsync(SaveChangesCompletedEventData eventData,intresult,
default)
{
Console.WriteLine("SavedChangesAsync");
returnbase.SavedChangesAsync(eventData, result, cancellationToken);
}
}

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

SavingChangesAsync
SavedChangesAsync

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

References

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/dev/src/WeihanLi.EntityFramework/EFExtensions.cs#L221-L237

https://github.com/WeihanLi/WeihanLi.EntityFramework/blob/015fa8de997add46f463fcb67fa9f5a9ada4b814/samples/WeihanLi.EntityFramework.Sample/Program.cs#L438-L454

来源:opendotnet

相关推荐