C# .NETCore:如何初始化需要DBContext的单例?
我有一个.Net核心服务(“MyLookup”),它执行数据库查询、一些Active Directory查找,并将结果存储到内存缓存中 在我的第一次剪切中,我在Startup.cs中执行了C# .NETCore:如何初始化需要DBContext的单例?,c#,.net-core,dependency-injection,C#,.net Core,Dependency Injection,我有一个.Net核心服务(“MyLookup”),它执行数据库查询、一些Active Directory查找,并将结果存储到内存缓存中 在我的第一次剪切中,我在Startup.cs中执行了.AddService(),将服务注入到使用该服务的每个控制器和视图的构造函数中。。。一切顺利 它之所以有效,是因为我的服务和它的依赖服务(IMemoryCache和DBContext)都是。但现在我想让这项服务成为一种服务。我想在应用程序初始化时初始化它(执行DB查询、AD查找,并将结果保存到内存缓存) 问:
.AddService()
,将服务注入到使用该服务的每个控制器和视图的构造函数中。。。一切顺利
它之所以有效,是因为我的服务和它的依赖服务(IMemoryCache和DBContext)都是。但现在我想让这项服务成为一种服务。我想在应用程序初始化时初始化它(执行DB查询、AD查找,并将结果保存到内存缓存)
问:我该怎么做
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDBContext")));
services.AddMemoryCache();
services.AddSingleton<IMyLookup, MyLookup>();
...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
// Q: Is this a good place to initialize my singleton (and perform the expensive DB/AD lookups?
app.ApplicationServices.GetService<IDILookup>();
public IndexModel(MyDBContext context, IMyLookup myLookup)
{
_context = context;
_myLookup = myLookup;
...
public class MyLookup : IMyLookup
...
public MyLookup (IMemoryCache memoryCache)
{
// Perform some expensive lookups, and save the results to this cache
_cache = memoryCache;
}
...
private async void Rebuild() // This should only get called once, when app starts
{
ClearCache();
var allNames = QueryNamesFromDB();
...
private List<string>QueryNamesFromDB()
{
// Q: ????How do I get "_context" (which is a scoped dependency)????
var allNames = _context.MyDBContext.Select(e => e.Name).Distinct().ToList<string>();
return allSInames;
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDBContext")));
services.AddMemoryCache();
// I got 80% of the way with .AddScoped()...
// ... but I couldn't invoke it from Startup.Configure().
services.AddSingleton<IMyLookup, MyLookup>();
...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// This finally worked successfully...
app.ApplicationServices.GetService<IMyLookup>().Rebuild();
public IndexModel(MyDBContext context, IMyLookup myLookup)
{
// This remained unchanged (for all consumers)
_context = context;
_myLookup = myLookup;
...
public interface IMyLookup
{
Task<List<string>> GetNames(string name);
Task Rebuild();
}
public class MyLookup : IMyLookup
{
private readonly IMemoryCache _cache;
private readonly IServiceScopeFactory _scopeFactory;
...
public MyLookup (IMemoryCache memoryCache, IServiceScopeFactory scopeFactory)
{
_cache = memoryCache;
_scopeFactory = scopeFactory;
}
private async void Rebuild()
{
ClearCache();
var allNames = QueryNamesFromDB();
...
private List<string>QueryNamesFromDB()
{
// .CreateScope() -instead of constructor DI - was the key to resolving the problem
using (var scope = _scopeFactory.CreateScope())
{
MyDBContext _context =
scope.ServiceProvider.GetRequiredService<MyDBContext>();
var allNames = _context.MyTable.Select(e => e.Name).Distinct().ToList<string>();
return allNames;
}
}
MyLookup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDBContext")));
services.AddMemoryCache();
services.AddSingleton<IMyLookup, MyLookup>();
...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
// Q: Is this a good place to initialize my singleton (and perform the expensive DB/AD lookups?
app.ApplicationServices.GetService<IDILookup>();
public IndexModel(MyDBContext context, IMyLookup myLookup)
{
_context = context;
_myLookup = myLookup;
...
public class MyLookup : IMyLookup
...
public MyLookup (IMemoryCache memoryCache)
{
// Perform some expensive lookups, and save the results to this cache
_cache = memoryCache;
}
...
private async void Rebuild() // This should only get called once, when app starts
{
ClearCache();
var allNames = QueryNamesFromDB();
...
private List<string>QueryNamesFromDB()
{
// Q: ????How do I get "_context" (which is a scoped dependency)????
var allNames = _context.MyDBContext.Select(e => e.Name).Distinct().ToList<string>();
return allSInames;
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDBContext")));
services.AddMemoryCache();
// I got 80% of the way with .AddScoped()...
// ... but I couldn't invoke it from Startup.Configure().
services.AddSingleton<IMyLookup, MyLookup>();
...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// This finally worked successfully...
app.ApplicationServices.GetService<IMyLookup>().Rebuild();
public IndexModel(MyDBContext context, IMyLookup myLookup)
{
// This remained unchanged (for all consumers)
_context = context;
_myLookup = myLookup;
...
public interface IMyLookup
{
Task<List<string>> GetNames(string name);
Task Rebuild();
}
public class MyLookup : IMyLookup
{
private readonly IMemoryCache _cache;
private readonly IServiceScopeFactory _scopeFactory;
...
public MyLookup (IMemoryCache memoryCache, IServiceScopeFactory scopeFactory)
{
_cache = memoryCache;
_scopeFactory = scopeFactory;
}
private async void Rebuild()
{
ClearCache();
var allNames = QueryNamesFromDB();
...
private List<string>QueryNamesFromDB()
{
// .CreateScope() -instead of constructor DI - was the key to resolving the problem
using (var scope = _scopeFactory.CreateScope())
{
MyDBContext _context =
scope.ServiceProvider.GetRequiredService<MyDBContext>();
var allNames = _context.MyTable.Select(e => e.Name).Distinct().ToList<string>();
return allNames;
}
}
MyLookup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDBContext")));
services.AddMemoryCache();
services.AddSingleton<IMyLookup, MyLookup>();
...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
// Q: Is this a good place to initialize my singleton (and perform the expensive DB/AD lookups?
app.ApplicationServices.GetService<IDILookup>();
public IndexModel(MyDBContext context, IMyLookup myLookup)
{
_context = context;
_myLookup = myLookup;
...
public class MyLookup : IMyLookup
...
public MyLookup (IMemoryCache memoryCache)
{
// Perform some expensive lookups, and save the results to this cache
_cache = memoryCache;
}
...
private async void Rebuild() // This should only get called once, when app starts
{
ClearCache();
var allNames = QueryNamesFromDB();
...
private List<string>QueryNamesFromDB()
{
// Q: ????How do I get "_context" (which is a scoped dependency)????
var allNames = _context.MyDBContext.Select(e => e.Name).Distinct().ToList<string>();
return allSInames;
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDBContext")));
services.AddMemoryCache();
// I got 80% of the way with .AddScoped()...
// ... but I couldn't invoke it from Startup.Configure().
services.AddSingleton<IMyLookup, MyLookup>();
...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// This finally worked successfully...
app.ApplicationServices.GetService<IMyLookup>().Rebuild();
public IndexModel(MyDBContext context, IMyLookup myLookup)
{
// This remained unchanged (for all consumers)
_context = context;
_myLookup = myLookup;
...
public interface IMyLookup
{
Task<List<string>> GetNames(string name);
Task Rebuild();
}
public class MyLookup : IMyLookup
{
private readonly IMemoryCache _cache;
private readonly IServiceScopeFactory _scopeFactory;
...
public MyLookup (IMemoryCache memoryCache, IServiceScopeFactory scopeFactory)
{
_cache = memoryCache;
_scopeFactory = scopeFactory;
}
private async void Rebuild()
{
ClearCache();
var allNames = QueryNamesFromDB();
...
private List<string>QueryNamesFromDB()
{
// .CreateScope() -instead of constructor DI - was the key to resolving the problem
using (var scope = _scopeFactory.CreateScope())
{
MyDBContext _context =
scope.ServiceProvider.GetRequiredService<MyDBContext>();
var allNames = _context.MyTable.Select(e => e.Name).Distinct().ToList<string>();
return allNames;
}
}
公共接口IMyLookup
{
任务名称(字符串名称);
任务重建();
}
公共类MyLookup:IMyLookup
{
专用只读IMemoryCache\u缓存;
专用读写器ViceScopeFactory\u scopeFactory;
...
公共MyLookup(IMemoryCache memoryCache,iSeries视图工厂范围工厂)
{
_cache=memoryCache;
_scopeFactory=scopeFactory;
}
私有异步void重建()
{
ClearCache();
var allNames=QueryNamesFromDB();
...
私有ListQueryNamesFromDB()
{
//.CreateScope()-而不是构造函数DI-是解决此问题的关键
使用(var scope=\u scopeFactory.CreateScope())
{
MyDBContext\u context=
scope.ServiceProvider.GetRequiredService();
var allNames=_context.MyTable.Select(e=>e.Name).Distinct().ToList();
返回所有名称;
}
}
您的问题没有单一的解决方案。其中有不同的原则,例如预防的思想,即组件只应依赖于具有相同或更长生命周期的服务。这种思想推动了拥有一个具有范围或暂时生活方式的MyLookup
类
这个想法可以归结为实践,这意味着您可以组合对象图,在图的组件的变量中捕获运行时数据。相反的组合模型是,它将状态保持在对象图之外,并允许根据需要检索状态(例如DbContext
)
但这都是理论。起初,可能很难将其转化为实践理论上,应用闭包组合模型很简单,因为它只是意味着给MyLookup
一个更短的生活方式,例如Scoped
。但是当MyLookup
本身捕获在应用程序期间需要重用的状态时,这似乎是不可能的
但通常情况并非如此。一种解决方案是将状态从MyLookup
提取到一个不包含任何依赖项(或仅依赖于单例)的依赖项中然后变成一个单例。MyLookup
可以被“降级”为Scoped
,并将运行时数据传递给它的单例依赖项进行缓存。我很想给您展示一个这样的例子,但您的问题需要更多细节才能做到这一点
但是,如果您想将MyLookup
保留为一个单例,那么肯定有很多方法可以做到这一点。例如,您可以将单个操作包装到一个作用域中。例如:
公共类MyLookup:IMyLookup
...
公共MyLookup(IMemoryCache memoryCache,iSeries视图工厂范围工厂)
{
_cache=memoryCache;
_scopeFactory=scopeFactory;
}
私有列表QueryNamesFromDB()
{
使用(var scope=\u scopeFactory.CreateScope())
{
var context=scope.ServiceProvider.GetRequiredService();
var allNames=context.Persons.Select(e=>e.Name.Distinct().ToList();
归还所有的钱;
}
}
}
在本例中,MyLookup
被注入IServiceScopeFactory
。这允许创建(和销毁)在单个调用中调用IServiceScope
。这种方法的缺点是MyLookup
现在需要依赖于DI容器。只有属于的类才应该知道DI容器的存在
因此,一种常见的方法是注入一个Func
依赖项。但是对于MS.DI来说,这实际上相当困难,因为当您尝试这种方法时,工厂的作用域是根容器,而您的DbContext
总是需要作用域。有一些方法可以解决这一问题,但由于sid的时间限制,我将不讨论这些方法e、 因为这会让我的答案复杂化
要从业务逻辑中分离对DI容器的依赖,您必须:
- 将这个完整的类移动到你的合成根目录中
- 或者将类一分为二,以允许业务逻辑保留在组合根之外;例如,您可以使用子类或组合来实现这一点
不要注入依赖项,而是注入一个工厂来创建该依赖项,并在每次需要实例时调用该工厂。
Q:听起来很有希望吗?Q:有没有链接指向如何在.Net Core中使用DBContext实现这一点的?为什么您需要将服务作为单一服务一开始是吨?很漂亮。特别感谢