AddSingleton、AddScoped与AddTransient的适用场景及实际案例说明
首先明确三种注册方式的核心生命周期差异:
AddTransient:每次从DI容器请求服务时,都会创建全新的实例AddScoped:同一个作用域(ASP.NET Core中默认对应单个HTTP请求)内仅创建一个实例,不同作用域的实例互相隔离AddSingleton:整个应用运行生命周期内仅创建一个实例,所有请求共享该实例
1. AddTransient
适用场景
无状态、轻量化、不需要跨调用共享状态的服务,创建销毁成本极低,每次使用后即可被回收。
真实应用案例:密码哈希工具类
这类服务只负责纯计算逻辑,没有任何需要保存的全局状态,每次调用的输出仅和输入参数有关,完全不需要复用实例。
代码示例
服务定义:
public interface IPasswordHasher { string Hash(string rawPassword); bool Verify(string rawPassword, string hashedPassword); } public class BcryptPasswordHasher : IPasswordHasher { // 无全局字段,所有逻辑都在方法内实现,完全无状态 public string Hash(string rawPassword) => BCrypt.Net.BCrypt.HashPassword(rawPassword, workFactor: 11); public bool Verify(string rawPassword, string hashedPassword) => BCrypt.Net.BCrypt.Verify(rawPassword, hashedPassword); }
注册代码:builder.Services.AddTransient<IPasswordHasher, BcryptPasswordHasher>();
注意:不要在单例服务中直接注入Transient服务,否则Transient实例会被单例持有,生命周期被拉长为单例,不符合预期
2. AddScoped
适用场景
需要在单个请求/作用域内共享状态,作用域结束后即可释放的服务,适合和单次请求绑定的上下文类、资源类服务。
真实应用案例1:EF Core 数据库上下文(DbContext)
EF Core的DbContext默认就是推荐注册为Scoped,同一个请求内的所有数据库操作共用同一个DbContext,可以保证事务一致性,避免多上下文带来的并发脏写问题,请求结束后自动释放上下文占用的数据库连接资源。
注册代码
builder.Services.AddScoped<AppDbContext>();
比如用户提交订单的接口中,先查询用户余额、再扣减余额、最后生成订单,三个操作在同一个请求内共用同一个DbContext,保证操作的原子性,不会出现查询和扣减用不同上下文导致的超扣问题。
真实应用案例2:当前请求用户上下文服务
很多业务场景下需要在控制器、领域服务、仓储层多个位置获取当前登录的用户信息,就可以定义一个Scoped的用户上下文服务,在请求的中间件阶段赋值,整个请求内的所有服务都可以共享同一个用户信息,不同请求之间互相隔离。
代码示例
服务定义:
public interface ICurrentUser { int UserId { get; set; } string UserName { get; set; } } public class CurrentUser : ICurrentUser { public int UserId { get; set; } public string UserName { get; set; } }
注册代码:builder.Services.AddScoped<ICurrentUser, CurrentUser>();
3. AddSingleton
适用场景
需要全局共享状态、初始化成本高、本身线程安全的服务,整个应用运行期间只需要初始化一次即可。
真实应用案例1:全局站点配置服务
站点的基础配置一般存在appsettings.json中,只需要在应用启动时读取一次即可,不需要每次用都重新读配置文件,适合注册为单例。
代码示例
配置类定义:
public class SiteConfig { public string SiteName { get; set; } public int MaxUploadFileSizeMb { get; set; } public string CustomerServiceEmail { get; set; } }
注册代码:builder.Services.AddSingleton(builder.Configuration.GetSection("SiteConfig").Get<SiteConfig>());
真实应用案例2:全局内存缓存服务
ASP.NET Core自带的IMemoryCache默认就是单例注册,所有请求共享同一个缓存池,才能实现缓存的复用效果,如果注册为Scoped或者Transient,每个请求单独维护缓存,就完全失去了缓存的意义。
注意:单例服务是多线程共享的,所有操作必须保证线程安全,避免出现并发问题;如果需要在单例服务中使用Scoped服务,必须手动创建作用域获取,不能直接注入
内容的提问来源于stack exchange,提问作者Dev Sharma

