Yes, but using a DbContext from an ILogger isn't the best idea. Loggers and log sinks are separate entities.
Using scoped services from a singleton isn't unusual. This is the case with Background services, which are registered as singletons. Consuming scoped services is explained in the section Consuming a scoped service in a background task.
To use a scoped (or transient) service, the singleton needs access to the IServiceProvider
. This is typically passed as a constructor dependency.
public class MyHostedService : BackgroundService
{
private readonly ILogger<MyHostedService> _logger;
public MyHostedService(IServiceProvider services,
ILogger<MyHostedService> logger)
{
Services = services;
_logger = logger;
}
}
This class can be used to retrieve transient instances using IServiceProvider.GetService or GetRequiredService:
var service = Services.GetRequiredService<MyTransientService>();
To use a scoped service the singleton needs to explicitly create the scope and retrieve the services from that scope. When the scope is disposed, any scoped instances will be disposed as well
using (var scope = Services.CreateScope())
{
var context =
scope.ServiceProvider
.GetRequiredService<MyDbContext>();
...
context.SaveChanges();
}
Loggers and databases
An ILogger doesn't need to depend on a DbContext. Logging libraries use separate interfaces to publish and store log entries. An ILogger
instance is used to publish a log entry, not store it. Storing the log event is the job of a log sink or log provider - different log libraries use different names for this.
.NET Core offers few log sinks intentionally. There are very good logging libraries like Serilog, and the .NET Core team had no intention of duplicating their work. They do offer a few sink implementations for Microsoft-specific services like Azure Application Insights.
One could write a custom log sink that writes to a database but it's probably better to use an existing library that already offers this functionality and takes care of buffering, async operations etc. Writing to a database is always more expensive than writing to a local file, so database sinks need to buffer messages to reduce the impact to the application.
It's easy to integrate one of the existing libraries like Serilog using eg Serilog.AspNetCore.
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>();
});
That library in turn offers database sinks like 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();
The SQL Server sink will create the log table and start writing log entries to it. The UseSerilog()
call will redirect all ILogger
calls to Serilog and eventually the database table.