单例 class 可以有非单例依赖吗?

Can a singleton class have a non-singleton dependency?

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

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

我的问题是:如果我的 ILoggerProvider 配置为单例,但它依赖于非单例的 ApplicationDbContext 会怎样?

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

从单例中使用作用域服务并不罕见。 Background services, which are registered as singletons. Consuming scoped services is explained in the section 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;
    }
}

此 class 可用于使用 IServiceProvider.GetService or 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 服务器接收器将创建日志 table 并开始向其中写入日志条目。 UseSerilog() 调用会将所有 ILogger 调用重定向到 Serilog 并最终重定向到数据库 table.