单例类可以有非单例依赖吗? [复制]
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 但单身人士也将永远活着。此外,它更多的是关于“不应该做”,而不是“不能做”。以上是关于单例类可以有非单例依赖吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章