使用 IServiceCollection.AddTransient、IServiceCollection.AddSingleton 和 IServiceCollectionAddScoped 方法的
Posted
技术标签:
【中文标题】使用 IServiceCollection.AddTransient、IServiceCollection.AddSingleton 和 IServiceCollectionAddScoped 方法的实际场景都有哪些?【英文标题】:What are the practical scenarios to use IServiceCollection.AddTransient, IServiceCollection.AddSingleton and IServiceCollectionAddScoped Methods?使用 IServiceCollection.AddTransient、IServiceCollection.AddSingleton 和 IServiceCollectionAddScoped 方法的实际场景有哪些? 【发布时间】:2021-09-12 14:43:36 【问题描述】:阅读this 的帖子后,我可以理解AddTransient
、AddScoped
和AddSingleton
之间的区别,但是我无法看到它们各自的实际用法。
我的理解是
添加瞬态
每次客户端请求时创建一个新实例。
services.AddTransient<IDataAccess, DataAccess>();
每次客户端代码请求时都会返回一个新的 DataAccess 对象。更有可能是构造函数。
AddTransient 的使用
如果我们必须访问数据库以读取和更新它并销毁访问对象 (DataAccess),最好使用 AddTransient
- 不确定线程安全性。
AddScoped
为每个 http web 请求创建一个新实例。
AddScoped 的使用
services.AddScoped<ShoppingCart>(serviceProvider => ShoppingCart.GetShoppingCart(serviceProvider));
这意味着每个网络请求都将拥有自己的购物车实例,实习生意味着每个用户/客户端都将拥有自己的购物车实例用于该 http 网络请求。
添加单例
为所有 http web 请求创建单个实例。
AddSingleton 的使用
在示例应用程序中找到了这段代码,但我不明白它有什么用处。
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
谁能给出一个合适的实际例子,何时使用 AddSingleton 并检查我对 AddTransient 和 AddScoped 的理解是否正确?
【问题讨论】:
【参考方案1】:您对所有 3 个作用域的理解都是正确的。
Transient 将在组件无法共享时使用。一个非线程安全的数据库访问对象就是一个例子。
Scoped 可用于实体框架数据库上下文。主要原因是从数据库中获取的实体将被附加到请求中所有组件看到的相同的上下文。当然,如果您打算并行执行查询,则不能使用 Scoped。
Scoped 对象的另一个例子是某种RequestContext
类,它包含例如调用者的用户名。中间件/MVC过滤器可以请求它并填写信息,其他组件也可以请求它,它肯定会包含当前请求的信息。
Singleton 组件总是共享的,因此它们最适合不需要绑定到请求的线程安全组件。一个例子是IOptions
,它可以访问配置设置。在单个静态 HttpClient
实例上使用 SendAsync
的 HttpClient
包装类也将是完全线程安全的,并且非常适合成为单例。
请注意,如果您有一个依赖于 Scoped 组件的 Singleton 组件,则它的依赖项将在它之前被释放。因此,一个组件不能依赖于另一个范围小于自身的组件。
【讨论】:
谢谢你,Juunas,一个很好的解释,但是我期待一些具体例子的答案 - 可能现在要求这个还为时过早。 如果我没看错的话,那么 Singleton 比 Scoped 和 Transient 更受欢迎(因为不必要地重新创建对象通常看起来没有必要),但对于我们不能使用它的情况(例如,因为非-线程安全代码),我们必须使用其他两个之一(取决于我们的需要)。 至少我是这样想的:) @PeppeL-G 如果将 DbContext 对象设置为 Transient 是否正确? DbConext 类来自实体框架,用于访问表视图等数据库对象... @YawarMurtaza 我会说是的,假设使用它的服务也被标记为瞬态。 EF 上下文可以是瞬态的或作用域的,而不是真正的单例。经常使用 Scoped 的原因是请求中的多个服务可以对同一个上下文实例进行修改和查询,最后 SaveChanges() 一次保存所有这些。瞬态意味着你不能这样做,但当然提供更高的隔离性:)【参考方案2】:我见过“只使用AddTransient<T>()
”的观点,但我不同意。
考虑内存分配
我讨厌在不需要的时候分配东西,所以如果我知道我正在创建线程安全的东西,或者我有明确的文档表明拥有单例实例是预期的用法,那么我正在创建单身人士。
AddSingleton()
这是作为单例的 ApplicationInsights TelemetryClient 实例。他们的文档说这行得通。
telemetryClient = new TelemetryClient(TelemetryConfiguration.Active);
services.AddSingleton<TelemetryClient>(telemetryClient);
在这个项目中,我也使用了 Azure 表存储,我发现将 CloudTableClient 创建为单例就可以了。我不需要为每个请求都创建它的实例。
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Configuration.GetValue<string>("storageAccountConnectionString"));
CloudTableClient someTableClient = storageAccount.CreateCloudTableClient();
services.AddSingleton<CloudTableClient>(someTableClient);
在某种意义上,它相当于一个类的只读静态属性,例如:
public static readonly CloudTableClient cloudTableClient = new CloudTableClient(...);
...在整个应用程序中只有一个实例,但通过使用services.AddSingleton<T>()
,我们可以使用依赖注入直接访问它。
AddScoped()
AddScoped<T>()
的一个例子是我想将获取 Application Insights 所需的 javascript 嵌入到网页中,但我使用 Content-Security-Policy,因此我需要在任何页面上放置一个随机数JavaScript。我有一些代码可以帮助我做到这一点。
services.AddScoped<ApplicationInsightsJsHelper>();
AddTransient()
我还没有发现需要使用AddTransient<T>()
做任何事情。可能是我没有想到我必须创建的东西,每次我需要它们时,作为“服务”......它们只是我新的变量。从某种意义上说,AddTransient<T>()
是对工厂模式的隐藏使用...而不是调用静态的MyServiceFactory.Create()
函数,依赖注入(有效地)为你做同样的事情。
【讨论】:
如帖子所说,一切看项目需求以上是关于使用 IServiceCollection.AddTransient、IServiceCollection.AddSingleton 和 IServiceCollectionAddScoped 方法的的主要内容,如果未能解决你的问题,请参考以下文章
在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?
Qt静态编译时使用OpenSSL有三种方式(不使用,动态使用,静态使用,默认是动态使用)