已实施第二种方法 ^^:这将要 DI'ed 的策略与现有存储库分离。扩展方法IDbConnection
负责围绕现有方法包装策略。
public class SqlResiliencePolicyFactory
{
private readonly ISet<int> _transientDbErrors = new HashSet<int>(new[] { 1205 });
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
public SqlResiliencePolicyFactory(ILogger logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}
public IPolicyRegistry<string> GetSqlResiliencePolicies(int transientErrorRetries = 3)
{
return new PolicyRegistry
{
{
"DbDeadLockResilience",
Policy
.Handle<SqlException>(ex => _transientDbErrors.Contains(ex.Number))
.WaitAndRetry(
retryCount: transientErrorRetries,
sleepDurationProvider: attempt => TimeSpan.FromMilliseconds(attempt * 100),
onRetry: LogRetryAction)
},
{
"DbDeadLockResilienceAsync",
Policy
.Handle<SqlException>(ex => _transientDbErrors.Contains(ex.Number))
.WaitAndRetryAsync(
retryCount: transientErrorRetries,
sleepDurationProvider: attempt => TimeSpan.FromMilliseconds(attempt * 100),
onRetry: LogRetryAction)
}
};
}
private void LogRetryAction(Exception exception, TimeSpan sleepTime, int reattemptCount, Context context) =>
_logger.Log(
LogLevel.Warning,
exception,
@$"Transient DB Failure while executing query,
error number: {((SqlException)exception).Number};
reattempt number: {reattemptCount}");
}
启动时:
DapperExtensions.SetPolicies(new SqlResiliencePolicyFactory(_logger, _configuration)
.GetSqlResiliencePolicies());
在单独的类中创建扩展方法,以将策略包装到存储库的现有方法中。
扩展方法:
public static class DapperExtensions
{
private static Policy _policy = Policy.NoOp();
private static IAsyncPolicy _asyncPolicy = Policy.NoOpAsync();
public static void SetPolicies(IReadOnlyPolicyRegistry<string> readOnlyPolicyRegistry)
{
_policy = readOnlyPolicyRegistry.Get<Policy>("DbDeadLockResilience");
_asyncPolicy = readOnlyPolicyRegistry.Get<IAsyncPolicy>("DbDeadLockResilienceAsync");
}
public static T GetFirstWithRetry<T>(this IDbConnection connection,
string? sql = null, object? parameters = null, IDbTransaction? transaction = null) where T : class =>
_policy.Execute(() => connection.GetFirst<T>(sql, parameters, transaction));
public static T QueryFirstOrDefaultWithRetry<T>(this IDbConnection connection, string sql,
object? parameters = null, IDbTransaction? transaction = null) =>
_policy.Execute(() => connection.QueryFirstOrDefault<T>(sql, parameters, transaction));
public static async Task<bool> UpdateAsyncWithRetry<T>(this IDbConnection connection, T entityToUpdate, IEnumerable<string> columnsToUpdate,
IDbTransaction? transaction = null) where T : class =>
await _asyncPolicy.ExecuteAsync(async () => await connection.UpdateAsync(entityToUpdate, columnsToUpdate, transaction));
//Similarly, add overloads to all the other methods in existing repo.
}
Now,
- 现有的回购协议独立于政策(没有 DI 到回购协议)。
- 政策按照 SRP 保存在单独的位置。
- Dapper 扩展可以更改策略以便于测试。
因此,现有的存储库必须更改名称并调用上面的包装器,而不是调用 dapper 方法本身,才会应用策略。不要忘记对存储库进行一次回归测试。