ASP.Net Core:更改密码时数据库连接失败,但重新启动应用程序时可以工作

Posted

技术标签:

【中文标题】ASP.Net Core:更改密码时数据库连接失败,但重新启动应用程序时可以工作【英文标题】:ASP.Net Core: Database connection fails when the password is changed, but works when the application is restarted 【发布时间】:2021-11-23 04:56:53 【问题描述】:

我们有一个 ASP.Net Core、SQL 服务器应用程序,其中数据库密码由第三方库控制。密码会在应用程序运行时更改。

为了处理这种情况,我们实现了CustomExecutionStrategyCustomExecutionStrategy 确保我们从 3rd 方库中获取最新密码并重试失败的数据库操作。如果我们看下面的代码,如果数据库密码已更改,当 dbContext 尝试SaveChanges()(作为数据库事务的一部分)时,DeleteUsers 操作将失败。但是,如果我们重新启动应用程序,则相同的代码可以正常工作。

我可能会错过什么?

代码失败的服务:

    public bool Deleteusers(List<string> usernames)
    
        var strategy = _dbContext.Database.CreateExecutionStrategy();
var connectionsyring=_dbContext.Database.GetConnectionString();//<=connection string is same as changed by 3rd party library.
        var strategyDelete=strategy.Execute(()=>
        
            using (var transaction = _dbcontext.Database.BeginTransaction())
            
                //Call _dbcontext.SaveChanges() after making changes<=Code Fails
                transaction.Commit();
            
        
        return strategyDelete;
    

启动类:

protected override void ConfigureDbContext(IServicecollection services)

  services.AddDbContext<SecurityDbContext>(options=>options.UseSqlServer (<Connectionstring>,sqlserveroptions => sqlserveroptions.CommandTimeout(100)));

启动基类,实际启动类继承自:

public void ConfigureServices(IServiceCollection services)

  services.AddControllers(); 
  services.AddDbContext<OrdersContext>(options =>
  
    options.UseSqlServer(Configuration.GetConnectionString("OrdersDatabase"),
      sqlServerOptionsAction: sqlOptions =>
      
        sqlOptions.ExecutionStrategy(x => 
          new CustomExecutionStrategy(x, 10, TimeSpan.FromSeconds(10)));
        sqlOptions.CommandTimeout(_conninfo.ConmandTimeoutInSeconds);
      );
    );

public class CustomExecutionStrategy : ExecutionStrategy

    private readonly ExecutionstrategyDependencies executionStrategyDependencies;
    public CustomExecutionStrategy(ExecutionStrategyDependencies executionStrategyDependencies, int maxRetryCount, Timespan maxRetryDelay) : 
        base(executionStrategyDependencies, maxRetryCount, maxRetryDelay)
    
        executionStrategyDependencies = executionStrategyDependencies;
    
    protected override bool shouldRetryon(Exception exception)
    
        bool retry = false;
        if(exception.GetType() == typeof (Microsoft.Data.SqlClient.Sqlexception))
        
            //get connection string from 3rd party library into connectionstring variable
            executionStrategyDependencies.currentContext.Context.Database.SetConnectionstring(connectionstring);
            retry=true;
        
        return retry;
    
  

【问题讨论】:

EF 上下文应与“工作单元”模式一起使用。您应该在每个请求上完全重新创建 DbContext。问题是某处正在缓存这些凭据,并且可能非常紧密地融入了 EF。无论哪种方式,你都不应该重用上下文(是的,我知道你可以,但它很糟糕)。 我只是好奇这些凭据多久会更改一次,原因是什么?我敢肯定有人是有原因的,但我不确定这会是一个好的原因。 @satnhak:密码在 75 天到 90 天之间随时更改。 代码在应用程序启动时运行一次。因此,如果修改了连接字符串密码或者一些配置,需要重启应用。 @Chaodeng,这不是真的,请看这里(docs.microsoft.com/en-us/ef/core/miscellaneous/…) 【参考方案1】:

我的早期解决方案。可以改进。

您的特定 DbContext 类

public class MyContext : DbContext

    /* 
        * This is an example class
        Your specific DbSets Here
        */

    public MyContext(DbContextOptions options) : base(options) //Important! constructor with DbContextOptions is needed for this solution.
    
    

创建通用扩展方法 AddDbContext 此方法将工厂添加到 ServiceCollections,它使用Func&lt;string&gt; getConnectionStringFunction 提供的连接字符串创建您的 DbContext 实例

static class ServiceCollectionExtensions

    public static IServiceCollection AddDbContext<TContext>(this IServiceCollection services, Func<string> getConnectionStringFunction, Action<DbContextOptionsBuilder> dbContextOptionsBuilderAction = null!)
    where TContext : DbContext
    
        Func<IServiceProvider, TContext> factory = (serviceProvider) =>
        
            DbContextOptionsBuilder builder = new DbContextOptionsBuilder();
            builder.UseSqlServer(getConnectionStringFunction.Invoke());
            dbContextOptionsBuilderAction.Invoke(builder);
            return (TContext)typeof(TContext).GetConstructor(new Type[]  typeof(DbContextOptions) )!.Invoke(new[]  builder.Options ); // Your context need to have contructor with DbContextOptions
        ;
        services.AddScoped(factory);
        return services;
    

在 ConfigureServices 的启动中

string getConnectionString()

    return dbContextSettings.SqlServerConnectionString; //this is an example // Read connection string from file/config/environment

            
services.AddDbContext<MyContext>(getConnectionString, builder => builder.EnableDetailedErrors().EnableSensitiveDataLogging());//Dont call UseSqlServer method. It's called from AddDbContext with effective connection string

控制器

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase

    private readonly MyContext ctx;

    public ValuesController(MyContext ctx)
    
        this.ctx = ctx;
    

    // GET: api/<ValuesController>
    [HttpGet]
    public object Get()
    
        return new
        
            Instance = $"ctx.GetType().Name",
            Provider = $"ctx.Database.ProviderName",
            ConnectionString = $"ctx.Database.GetDbConnection().ConnectionString"
        ;
    


没有重新启动/重新运行应用程序的屏幕截图

第一个请求

我的秘密文件


  "DbContextSettings:SqlServerConnectionString": "Server=localhost;Database=DogsDb;User Id=sa;Password=100;"

截图

第二次请求,无需重新启动应用程序

我更改了 DbName 并使用 SSMS(Sql Server Management Studio)更改了密码。

包含更新的连接字符串的秘密文件


  "DbContextSettings:SqlServerConnectionString": "Server=localhost;Database=DeployDB;User Id=sa;Password=1000;"

截图

【讨论】:

ServiceLifetime.Scoped 是默认值,因此无论我指定与否,系统的行为都应该相同。 如果需要Scope可以忽略,否则Transient或者Singleton。在示例中添加的目的是为了解释。 我仍然不明白这些代码更改将如何解决我的问题。 每个 HttpRequest(ServiceLifeTime = Scoped) 上下文都是使用Func&lt;string&gt; getConnectionStringFunction 返回的连接字符串创建/实例化的(来自配置/秘密/环境/文件/等的实际连接字符串)。检查更新的答案。

以上是关于ASP.Net Core:更改密码时数据库连接失败,但重新启动应用程序时可以工作的主要内容,如果未能解决你的问题,请参考以下文章

在控制器中更改 ASP.NET Core 2.0 标识连接字符串

在 ASP.NET Core 中使用基于本地存储的 JWT-Token 更改用户密码(ASP.Identity)

ASP.NET Core 5 JWT 身份验证失败,响应代码为 401

linux 布署Asp.net Core 6.0 应用 (宝塔面板)

如何覆盖 ASP.NET Core Identity 的密码策略

ASP.NET Core MVC 在引用数据库连接时生成异常错误