You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在中间件设置后维持HospitalContext的连接字符串状态?

问题分析与解决方案

当前问题的核心是:中间件与控制器获取的HospitalContext并非同一实例,且ConnectionsProvider存在线程安全隐患和赋值bug,导致连接字符串状态无法在请求内维持。以下是针对性的修复方案:


第一步:修复ConnectionsProvider的基础问题

原代码中SetConnection方法存在参数名与字段名冲突的bug,且注册为Singleton会导致多请求共享连接字符串,引发并发问题。

修改后的ConnectionsProvider代码

public interface IConnectionsProvider
{        
    string GetConnection();
    void SetConnection(string tagConnection);
    string TagConnection { get; set; }
}

public class ConnectionsProvider : IConnectionsProvider
{
    // 使用自动属性简化代码,避免字段与参数名冲突
    public string TagConnection { get; set; }

    public ConnectionsProvider(){}
    
    public ConnectionsProvider(string tagConnection)
    {
        TagConnection = tagConnection;
    }

    public string GetConnection() => TagConnection;

    public void SetConnection(string tagConnection)
    {
        // 明确赋值给实例字段
        this.TagConnection = tagConnection;
    }
}

第二步:重构HospitalContext的配置方式

让DbContext通过依赖注入获取当前请求的连接信息,而非手动传入字符串,确保请求内的上下文实例共享同一连接配置。

方案1:通过OnConfiguring动态获取连接字符串

public class HospitalContext : DbContext
{
    private readonly IConnectionsProvider _connectionsProvider;
    private DataConnection _dataConnection;

    // 依赖注入构造函数,接收请求级别的IConnectionsProvider
    public HospitalContext(IConnectionsProvider connectionsProvider)
    {
        _connectionsProvider = connectionsProvider;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // 仅在未配置时执行,避免重复配置
        if (!optionsBuilder.IsConfigured)
        {
            var connectionTag = _connectionsProvider.GetConnection();
            _dataConnection = connectionProvider.GetConnectionProvider(connectionTag);
            
            // 使用字符串插值简化拼接
            var connectionString = $"Server={_dataConnection.serverName};Database={_dataConnection.dataBaseDefault};User ID={_dataConnection.userName};Password={_dataConnection.userPasword}";
            optionsBuilder.UseSqlServer(connectionString);
        }
        base.OnConfiguring(optionsBuilder);
    }
}

方案2(推荐):通过DbContextOptions预配置

更符合EF Core最佳实践,直接在DI注册时完成上下文配置:

// HospitalContext构造函数改为接收DbContextOptions
public class HospitalContext : DbContext
{
    public HospitalContext(DbContextOptions<HospitalContext> options) : base(options)
    {
    }
}

第三步:修改中间件与DI注册

中间件调整(仅负责设置连接字符串)

无需手动创建HospitalContext,由DI容器管理实例生命周期:

public class ConnectionMiddleware
{
    private readonly RequestDelegate _next;

    public ConnectionMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context, IConnectionsProvider connectionsProvider)
    {            
        // 从请求头获取连接标识
        if (!context.Request.Headers.TryGetValue("Conexion", out var connectionTag))
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
            await context.Response.WriteAsync("缺少Conexion请求头");
            return;
        }

        // 设置当前请求的连接标识
        connectionsProvider.SetConnection(connectionTag);
        await _next(context);
    }      
}

Startup服务注册调整

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddCors();
    
    // 将ConnectionsProvider注册为Scoped,每个请求一个独立实例
    services.AddScoped<IConnectionsProvider, ConnectionsProvider>();

    // 方案1:注册HospitalContext(配合OnConfiguring方式)
    services.AddScoped<HospitalContext>();

    // 方案2:注册HospitalContext(配合DbContextOptions方式)
    // services.AddScoped<HospitalContext>(provider =>
    // {
    //     var connectionsProvider = provider.GetRequiredService<IConnectionsProvider>();
    //     var connectionTag = connectionsProvider.GetConnection();
    //     var dataConnection = connectionProvider.GetConnectionProvider(connectionTag);
    //     var options = new DbContextOptionsBuilder<HospitalContext>()
    //         .UseSqlServer($"Server={dataConnection.serverName};Database={dataConnection.dataBaseDefault};User ID={dataConnection.userName};Password={dataConnection.userPasword}")
    //         .Options;
    //     return new HospitalContext(options);
    // });
    
    services.AddAuthentication("BasicAuthentication")
            .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

    services.AddScoped<IUserService, UserService>();
}

中间件管道配置

确保中间件在路由和控制器之前执行:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();
    app.UseMiddleware<ConnectionMiddleware>(); // 放在路由前
    
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

第四步:控制器调整

直接注入DI提供的HospitalContext即可,无需手动处理连接:

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly HospitalContext _hospitalContext;

    public WeatherForecastController(HospitalContext dbContext)
    {
        _hospitalContext = dbContext;
    }

    [HttpGet]
    public async Task<IActionResult> Get()
    {
        // 直接使用配置好的上下文查询数据
        var data = await _hospitalContext.SomeTable.ToListAsync();
        return Ok(data);
    }
}

核心原理说明

  1. Scoped生命周期:IConnectionsProvider和HospitalContext都注册为Scoped,确保每个请求拥有独立的实例,避免并发冲突。
  2. DI实例共享:中间件设置的连接字符串会被当前请求的IConnectionsProvider实例持有,后续DI创建的HospitalContext会自动使用该实例的连接信息,保证请求内上下文状态一致。
  3. 避免手动实例化:由DI容器统一管理HospitalContext的创建,确保控制器和中间件使用同一实例。

内容的提问来源于stack exchange,提问作者Emerson Rios

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 08:05:03