单例类可以有非单例依赖吗? [复制]

Posted

技术标签:

【中文标题】单例类可以有非单例依赖吗? [复制]【英文标题】:Can a singleton class have a non-singleton dependency? [duplicate] 【发布时间】:2021-12-08 09:34:00 【问题描述】:

我想知道IServiceCollection.AddDbContext() 如何添加我的ApplicationDbContext。我的猜测是它没有作为单例添加,并且每个请求都会创建一个新实例。

我正在实现一个自定义ILoggerProvider,它需要一个ApplicationDbContext 的实例。

我的问题是:如果我的 ILoggerProvider 配置为单例,但它依赖于不是单例的 ApplicationDbContext,会发生什么情况?

【问题讨论】:

顺便说一句,让你的记录器依赖DbContext 可能是个坏主意,你应该非常小心。使用内置 SQL 提供程序的适当日志库会更安全。 扩展@DavidG 的评论 - 你真的应该考虑 Serilog。但老实说,您应该真正考虑使用关系数据库是否是一个好主意。查看 Serilog 接收器以获得一个想法:nuget.org/packages?q=serilog 一般来说,日志记录应该尽可能地与应用程序本身分开,并且您试图将它与您的数据库联系起来(这可能是中断的事情并且您可能需要记录该事件) . @JonathanWood 你的实际用例是什么?如果您想轻松跟踪操作、如果您有多个活动的作业或服务,或者如果您想收集和关联来自多个进程的事件,那么记录到数据库是非常有用的。它也可以用于分布式跟踪到一个点,多个独立的服务将足够重要的消息写入同一个日志数据库。 @PanagiotisKanavos:对于我的应用程序可能认为值得注意的任何内容,我都希望选择记录到数据库中。 【参考方案1】:

是的,但是使用 ILogger 中的 DbContext 并不是最好的主意。记录器和日志接收器是独立的实体。

在单例中使用作用域服务并不罕见。 Background services 就是这种情况,它被注册为单例。 Consuming a scoped service in a background task 部分解释了使用范围服务。

要使用作用域(或瞬态)服务,单例需要访问IServiceProvider。这通常作为构造函数依赖项传递。

public class MyHostedService : BackgroundService

    private readonly ILogger<MyHostedService> _logger;

    public MyHostedService(IServiceProvider services, 
        ILogger<MyHostedService> logger)
    
        Services = services;
        _logger = logger;
    

该类可用于使用IServiceProvider.GetService 或GetRequiredService 检索瞬态实例:

var service = Services.GetRequiredService<MyTransientService>();

要使用作用域服务,单例需要显式创建作用域并从该作用域检索服务。当作用域被释放时,所有作用域实例也将被释放

using (var scope = Services.CreateScope())

    var context = 
        scope.ServiceProvider
            .GetRequiredService<MyDbContext>();

    ...
    context.SaveChanges();

记录器和数据库

ILogger 不需要依赖于 DbContext。日志库使用单独的接口来发布和存储日志条目。 ILogger 实例用于发布日志条目,而不是存储它。存储日志事件是日志接收器或日志提供者的工作 - 不同的日志库为此使用不同的名称。

.NET Core 有意提供很少的日志接收器。有像 Serilog 这样非常好的日志库,.NET Core 团队无意复制他们的工作。他们确实为 Azure Application Insights 等 Microsoft 特定服务提供了一些接收器实现。

可以编写一个写入数据库的自定义日志接收器,但最好使用已经提供此功能并负责缓冲、异步操作等的现有库。写入数据库总是比写入数据库更昂贵本地文件,因此数据库接收器需要缓冲消息以减少对应用程序的影响。

使用 Serilog.AspNetCore 等现有库之一很容易集成,例如 Serilog。

public static int Main(string[] args)

    Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
        .Enrich.FromLogContext()
        .WriteTo.Console()
        .CreateLogger();
    ....


public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseSerilog() // <-- Add this line
            .ConfigureWebHostDefaults(webBuilder =>
            
                webBuilder.UseStartup<Startup>();
            );

该库反过来提供像 Serilog.Sinks.SqlServer 这样的数据库接收器。

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .WriteTo.MSSqlServer(
        connectionString: "Server=localhost;Database=LogDb;Integrated Security=SSPI;",
        sinkOptions: new MSSqlServerSinkOptions  TableName = "LogEvents" )
    .CreateLogger();

SQL Server 接收器将创建日志表并开始向其中写入日志条目。 UseSerilog() 调用会将所有 ILogger 调用重定向到 Serilog,并最终重定向到数据库表。

【讨论】:

其实transient服务不需要自定义作用域,可以从root解析,所以不需要IServiceProvider就可以直接注入。 @GuruStron 但是默认情况下 EF 是作为 Scoped 注入的。虽然我更喜欢将其作为瞬态注入,但个人 @CamiloTerevinto 我指的是答案中的特定语句(“要使用范围(或瞬态)服务,单例需要访问 IServiceProvider”)在我看来,这不是真的。 @DavidG 我想念something吗? @DavidG 但单身人士也将永远活着。此外,它更多的是关于“不应该做”,而不是“不能做”。

以上是关于单例类可以有非单例依赖吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

单例类和静态类的区别? [复制]

单例类与静态方法和字段? [复制]

实现接口的单例类[重复]

swift中的单例类变量? [复制]

ruby 中的单例类到底是啥?

在打开的单例类中调用单例类方法