英文:
Use DI inside the DbContext
问题
你的服务在这个地方注入是没问题的。你需要在 SaveChangesAsync
方法中获取服务的正确方式。尝试使用 this.GetInfrastructure().GetRequiredService<...>()
替代 this.GetService<...>()
:
var myService = this.GetInfrastructure().GetRequiredService<IMyService>();
英文:
I'm using ef core 6 and I want to get a service from DI inside my db context. This is my DbContext:
public class MyDbContext : DbContext
{
public MyDbContext (DbContextOptions<MyDbContext> opt) : base(opt)
{
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var myService = this.GetService<IMyService>();
// Do some stuffs before saving with myService
var result = await base.SaveChangesAsync(cancellationToken);
return result;
}
}
The IMyService
and MyDbContext
already introduced with the DI:
services.AddScoped<IMyService, MyService>();
services.AddDbContextPool<MyDbContext>(opt => opt.UseSqlServer(connectionString));
The service doesn't inject, and this line throws an error:
var myService = this.GetService<IMyService>();
Error:
Cannot resolve scoped service 'IMyService' from root provider
What should I do? Do I miss something?
答案1
得分: 2
DI(依赖注入)与 DbContexts(数据库上下文)的工作方式与任何其他注入的服务一样 - 通过将依赖项添加为构造函数参数。
从评论中看来,你实际想要的是在 EF Core 中使用二级缓存(在会话/事务/工作单元之间),这并不是开箱即用的功能,而且一般来说,这个概念现在不如几年前那么流行。ORM(对象关系映射)通常不用于与非关系数据库进行交互,所以应用程序通常在更高层次上使用单独的对象缓存。
有一些 NuGet 包可以将二级缓存添加到 EF Core,例如 EFCoreSecondLevelCacheInterceptor。该项目使用 EF Core DbCommand 拦截器 来跟踪和缓存由 EF Core 加载或持久化的数据。示例页面展示了如何使用内存或 Redis 缓存。
一旦配置了缓存,将其添加到 DbContext 很容易:
public static class MsSqlServiceCollectionExtensions
{
public static IServiceCollection AddConfiguredMsSqlDbContext(this IServiceCollection services, string connectionString)
{
services.AddDbContextPool<ApplicationDbContext>((serviceProvider, optionsBuilder) =>
optionsBuilder
.UseSqlServer(
connectionString,
sqlServerOptionsBuilder =>
{
sqlServerOptionsBuilder
.CommandTimeout((int)TimeSpan.FromMinutes(3).TotalSeconds)
.EnableRetryOnFailure()
.MigrationsAssembly(typeof(MsSqlServiceCollectionExtensions).Assembly.FullName);
})
.AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>()));
return services;
}
}
相关部分仅添加了 DbCommand 拦截器 SecondLevelCacheInterceptor
:
.AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>());
该包允许缓存特定查询的结果:
var post1 = context.Posts
.Where(x => x.Id > 0)
.OrderBy(x => x.Id)
.Cacheable(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(5))
.FirstOrDefault();
它还可以配置为缓存所有查询:
services.AddEFSecondLevelCache(options =>
{
options.UseMemoryCacheProvider().DisableLogging(true).UseCacheKeyPrefix("EF_");
options.CacheAllQueries(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30));
});
缓存特定查询:
services.AddEFSecondLevelCache(options =>
{
options.UseMemoryCacheProvider().DisableLogging(true).UseCacheKeyPrefix("EF_")
/*.CacheQueriesContainingTypes(
CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30), TableTypeComparison.Contains,
typeof(Post), typeof(Product), typeof(User)
)*/
.CacheQueriesContainingTableNames(
CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30), TableNameComparison.ContainsOnly,
"posts", "products", "users"
);
});
或者避免缓存特定查询:
services.AddEFSecondLevelCache(options =>
{
options.UseMemoryCacheProvider().DisableLogging(true).UseCacheKeyPrefix("EF_")
// 如何跳过缓存特定命令
.SkipCachingCommands(commandText =>
commandText.Contains("NEWID()", StringComparison.InvariantCultureIgnoreCase));
});
英文:
DI works the same way with DbContexts as any other injected service - by adding the dependency as a constructor parameter.
From the comments it seems that what you actually want is to use second-level caching (between sessions/transactions/Units-of-Work) with EF Core. This isn't available out of the box and in general, is a concept that isn't as popular now as it was some years ago. ORMs aren't used to talk to non-relational databases so applications use separate object caching at a higher level instead.
There are some NuGet packages that do add second-level caching to EF Core, for example EFCoreSecondLevelCacheInterceptor. This project uses EF Core DbCommand interceptors to track and cache the data loaded or persisted by EF Core. The landing page examples show how to use either in-memory or Redis caching.
Once you configure caching, adding it to a DbContext is easy :
public static class MsSqlServiceCollectionExtensions
{
public static IServiceCollection AddConfiguredMsSqlDbContext(this IServiceCollection services, string connectionString)
{
services.AddDbContextPool<ApplicationDbContext>((serviceProvider, optionsBuilder) =>
optionsBuilder
.UseSqlServer(
connectionString,
sqlServerOptionsBuilder =>
{
sqlServerOptionsBuilder
.CommandTimeout((int)TimeSpan.FromMinutes(3).TotalSeconds)
.EnableRetryOnFailure()
.MigrationsAssembly(typeof(MsSqlServiceCollectionExtensions).Assembly.FullName);
})
.AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>()));
return services;
}
}
The relevant part only adds the DbCommand interceptor SecondLevelCacheInterceptor
.AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>()));
The package allows caching the results of specific queries :
var post1 = context.Posts
.Where(x => x.Id > 0)
.OrderBy(x => x.Id)
.Cacheable(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(5))
.FirstOrDefault();
It can also be configured to cache all queries:
services.AddEFSecondLevelCache(options =>
{
options.UseMemoryCacheProvider().DisableLogging(true).UseCacheKeyPrefix("EF_");
options.CacheAllQueries(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30));
});
Specific queries :
services.AddEFSecondLevelCache(options =>
{
options.UseMemoryCacheProvider().DisableLogging(true).UseCacheKeyPrefix("EF_")
/*.CacheQueriesContainingTypes(
CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30), TableTypeComparison.Contains,
typeof(Post), typeof(Product), typeof(User)
)*/
.CacheQueriesContainingTableNames(
CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30), TableNameComparison.ContainsOnly,
"posts", "products", "users"
);
});
Or avoid caching specific queries
services.AddEFSecondLevelCache(options =>
{
options.UseMemoryCacheProvider().DisableLogging(true).UseCacheKeyPrefix("EF_")
// How to skip caching specific commands
.SkipCachingCommands(commandText =>
commandText.Contains("NEWID()", StringComparison.InvariantCultureIgnoreCase));
});
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论