C# .NETCore:如何初始化需要DBContext的单例?

C# .NETCore:如何初始化需要DBContext的单例?,c#,.net-core,dependency-injection,C#,.net Core,Dependency Injection,我有一个.Net核心服务(“MyLookup”),它执行数据库查询、一些Active Directory查找,并将结果存储到内存缓存中 在我的第一次剪切中,我在Startup.cs中执行了.AddService(),将服务注入到使用该服务的每个控制器和视图的构造函数中。。。一切顺利 它之所以有效,是因为我的服务和它的依赖服务(IMemoryCache和DBContext)都是。但现在我想让这项服务成为一种服务。我想在应用程序初始化时初始化它(执行DB查询、AD查找,并将结果保存到内存缓存) 问:

我有一个.Net核心服务(“MyLookup”),它执行数据库查询、一些Active Directory查找,并将结果存储到内存缓存中

在我的第一次剪切中,我在Startup.cs中执行了
.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容器的依赖,您必须:

  • 将这个完整的类移动到你的合成根目录中
  • 或者将类一分为二,以允许业务逻辑保留在组合根之外;例如,您可以使用子类或组合来实现这一点

你绝对应该阅读。你也应该阅读。我已经读了很多书,但我没有看到任何一个链接。谢谢:)重要提示:大多数示例都有“视图”或“控制器”。在调用任何视图或控制器之前,我需要初始化缓存。我需要使用.Net Core DI;我不能使用“Ninject”(如果这很重要的话)。也许我甚至可以在没有任何DI的情况下获得DBContext:)@Steven-您指向的一个链接建议
不要注入依赖项,而是注入一个工厂来创建该依赖项,并在每次需要实例时调用该工厂。
Q:听起来很有希望吗?Q:有没有链接指向如何在.Net Core中使用DBContext实现这一点的?为什么您需要将服务作为单一服务一开始是吨?很漂亮。特别感谢