依赖注入下IDbConnection/SqlConnection偶发ConnectionString未初始化异常排查
System.InvalidOperationException: 'The ConnectionString property has not been initialized.'异常 我来帮你定位这个偶发异常的核心问题——它其实是DI生命周期不匹配+错误的连接Dispose方式导致的:
你把IDbConnection注册为Transient(每次注入都会生成新实例),但你的EmployeeRepository是Scoped的(同一个HTTP请求内只会创建一次Repository实例)。这意味着:同一个请求里,Repository拿到的IDbConnection实例会跟着它存活整个请求周期。但你在GetAllActiveEmployees里写了using (var db = _connection)——这行代码会直接把注入进来的_connection实例给Dispose掉!
如果在同一个请求里,这个Repository的其他方法(或者同一个方法被多次调用)再去使用_connection,就会因为连接已经被释放、内部状态被重置,从而抛出ConnectionString未初始化的异常。这就是为什么异常是偶发的——只有当请求内出现多次复用Repository实例的场景时才会触发。
两种可行的解决方案
方案1:使用连接工厂(推荐,最规范)
不要直接注入IDbConnection,而是注入一个可以创建新连接的工厂方法,这样每次需要连接时都能拿到全新的实例,用完自动Dispose:
- 先修改DI注册代码:
public void ConfigureServices(IServiceCollection services) { // 注册连接工厂,每次调用都会生成新的SqlConnection实例 services.AddTransient<Func<IDbConnection>>(() => () => new SqlConnection(Configuration.GetConnectionString("AppConnectionString"))); // 其他服务注册保持不变 services.AddScoped<IAppConfigurationRepository, AppConfigurationRepository>(); services.AddScoped<IHomeworkingRequestRepository, HomeworkingRequestRepository>(); services.AddScoped<IEmployeeRepository, EmployeeRepository>(); services.AddScoped<IEmployeeService, EmployeeService>(); services.AddScoped<IHomeworkingRequestService, HomeworkingRequestService>(); services.AddMvc(); }
- 然后调整
EmployeeRepository的实现:
public class EmployeeRepository : IEmployeeRepository { private readonly Func<IDbConnection> _connectionFactory; // 注入连接工厂,而不是直接注入连接 public EmployeeRepository(Func<IDbConnection> connectionFactory) { _connectionFactory = connectionFactory; } public IEnumerable<Employee> GetAllActiveEmployees() { string query = @" SELECT FirstName ,LastName ,BusinessUnit FROM Employees"; // 每次调用都创建新连接,using会自动释放资源 using (var db = _connectionFactory()) { db.Open(); return db.Query<Employee>(query); } } }
方案2:避免Dispose注入的连接(不推荐,有资源隐患)
如果你暂时不想改DI注册,那可以去掉Repository里的using语句——因为注入的连接会在请求结束时由DI容器自动释放:
public IEnumerable<Employee> GetAllActiveEmployees() { string query = @" SELECT FirstName ,LastName ,BusinessUnit FROM Employees"; _connection.Open(); return _connection.Query<Employee>(query); }
但这个方案有明显隐患:如果请求内多次调用Repository的方法,会重复调用Open()导致异常,而且连接资源不能及时释放,可能引发连接池耗尽的问题。
为什么之前改Transient能暂时解决?
之前改成Transient时,你的请求流程里可能每个Repository方法只被调用一次,Dispose连接后没有后续复用的场景。但当业务逻辑变化,同一个请求内需要多次调用Repository方法时,就会触发这个偶发异常。
内容的提问来源于stack exchange,提问作者Florian

