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 服务器应用程序,其中数据库密码由第三方库控制。密码会在应用程序运行时更改。
为了处理这种情况,我们实现了CustomExecutionStrategy
。 CustomExecutionStrategy
确保我们从 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<string> 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<string> 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 应用 (宝塔面板)