汎用リポジトリを実装しましたが、デッドロック例外が発生した場合に再試行ロジックを実装するスマートな方法があるかどうか疑問に思っていましたか?
このアプローチは、すべてのリポジトリメソッドで同じにする必要があります。とにかく、すべての単一のメソッドで、「try/catch-再試行回数を指定してメソッドを再度呼び出す」の記述を回避できますか?
どんな提案も大歓迎です。
私のリポジトリコードの少し:
public class GenericRepository : IRepository
{
private ObjectContext _context;
public List<TEntity> ExecuteStoreQuery<TEntity>(string commandText, params object[] parameters) where TEntity : class
{
List<TEntity> myList = new List<TEntity>();
var groupData = _context.ExecuteStoreQuery<TEntity>(commandText, parameters);
return myList;
}
public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
{
var entityName = GetEntityName<TEntity>();
return _context.CreateQuery<TEntity>(entityName);
}
public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class
{
return GetQuery<TEntity>().AsEnumerable();
}
編集:
1.Solution:
から少し変更されましたchris.house. 'ssolution
public static T DeadlockRetryHelper<T>(Func<T> repositoryMethod, int maxRetries)
{
var retryCount = 0;
while (retryCount < maxRetries)
{
try
{
return repositoryMethod();
}
catch (System.Data.SqlClient.SqlException ex)
{
if (ex.Number == 1205)// Deadlock
retryCount++;
else
throw;
}
}
return default(T);
}
そしてあなたはそれをこのように呼びます:
public TEntity FirstOrDefault<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
return RetryUtility.DeadlockRetryHelper<TEntity>( () =>p_FirstOrDefault<TEntity>(predicate), 3);
}
protected TEntity p_FirstOrDefault<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
return GetQuery<TEntity>().FirstOrDefault<TEntity>(predicate);
}
このようなものはどうですか:
public T DeadlockRetryHelper<T>(Func<T> repositoryMethod, int maxRetries)
{
int retryCount = 0;
while (retryCount < maxRetries)
{
try
{
return repositoryMethod();
}
catch (SqlException e) // This example is for SQL Server, change the exception type/logic if you're using another DBMS
{
if (e.Number == 1205) // SQL Server error code for deadlock
{
retryCount++;
}
else
{
throw; // Not a deadlock so throw the exception
}
// Add some code to do whatever you want with the exception once you've exceeded the max. retries
}
}
}
上記のコードでは、再試行ロジックはすべてこのメソッド内にあり、リポジトリメソッドをデリゲートとして渡すだけです。
私はこれが古い投稿であることを知っていますが、更新された回答を共有したいと思いました。
EF 6には組み込みのソリューションがあり、1回限りの実装となる実行戦略を設定できます。 DbExectutionStrategyから継承し、ShouldRetryOn仮想メソッドをオーバーライドするクラスを作成します。適格な再試行コードである定数フィールド値を含む例外の静的クラスを作成し、それぞれをループして、スローされている現在のSQL例外が適格な再試行コードのリストと一致するかどうかを判断できます...
public static class SqlRetryErrorCodes
{
public const int TimeoutExpired = -2;
public const int Deadlock = 1205;
public const int CouldNotOpenConnection = 53;
public const int TransportFail = 121;
}
public class MyCustomExecutionStrategy : DbExecutionStrategy
{
public MyCustomExecutionStrategy(int maxRetryCount, TimeSpan maxDelay) : base(maxRetryCount, maxDelay) { }
private readonly List<int> _errorCodesToRetry = new List<int>
{
SqlRetryErrorCodes.Deadlock,
SqlRetryErrorCodes.TimeoutExpired,
SqlRetryErrorCodes.CouldNotOpenConnection,
SqlRetryErrorCodes.TransportFail
};
protected override bool ShouldRetryOn(Exception exception)
{
var sqlException = exception as SqlException;
if (sqlException != null)
{
foreach (SqlError err in sqlException.Errors)
{
// Enumerate through all errors found in the exception.
if (_errorCodesToRetry.Contains(err.Number))
{
return true;
}
}
}
return false;
}
}
最後に、カスタム実行戦略を設定したら、実行戦略を設定するパブリックコンストラクターを使用して、DbConfigurationから継承する別のクラスを作成するだけです。
public class MyEfConfigurations : DbConfiguration
{
public MyEfConfigurations()
{
SetExecutionStrategy("System.Data.SqlClient",() => new MyCustomExecutionStrategy(5,TimeSpan.FromSeconds(10)));
}
}
EntityFramework 6
ExecutionStrategy
機能を追加します。必要なのは、戦略を適切に設定することだけです。
私の再試行ポリシー:
public class EFRetryPolicy : DbExecutionStrategy
{
public EFRetryPolicy() : base()
{
}
//Keep this constructor public too in case it is needed to change defaults of exponential back off algorithm.
public EFRetryPolicy(int maxRetryCount, TimeSpan maxDelay): base(maxRetryCount, maxDelay)
{
}
protected override bool ShouldRetryOn(Exception ex)
{
bool retry = false;
SqlException sqlException = ex as SqlException;
if (sqlException != null)
{
int[] errorsToRetry =
{
1205, //Deadlock
-2, //Timeout
};
if (sqlException.Errors.Cast<SqlError>().Any(x => errorsToRetry.Contains(x.Number)))
{
retry = true;
}
}
return retry;
}
}
EFに私の戦略を適用するように伝えます。
public class EFPolicy: DbConfiguration
{
public EFPolicy()
{
SetExecutionStrategy(
"System.Data.SqlClient",
() => new EFRetryPolicy());
}
}
出典:
ここ で説明されているように、再試行戦略はユーザーが開始したトランザクション(TransactionScope
で作成されたトランザクション)では機能しません。使用すると、エラーThe configured execution strategy does not support user initiated transactions
解決策は機能しますが、廃止されるAction
またはFunc
への引数の数について心配する必要はありません。ジェネリックAction
を使用して単一の再試行メソッドを作成する場合、ラムダで呼び出されるメソッドのすべての可変性を処理できます。
public static class RetryHelper
{
public static void DeadlockRetryHelper(Action method, int maxRetries = 3)
{
var retryCount = 0;
while (retryCount < maxRetries)
{
try
{
method();
return;
}
catch (System.Data.SqlClient.SqlException ex)
{
if (ex.Number == 1205)// Deadlock
{
retryCount++;
if (retryCount >= maxRetries)
throw;
// Wait between 1 and 5 seconds
Thread.Sleep(new Random().Next(1000, 5000));
}
else
throw;
}
}
}
}
次に、次のように使用します。
RetryHelper.DeadlockRetryHelper(() => CopyAndInsertFile(fileModel));
何らかの形のポリシー注入を検討しましたか?例として、Unityインターセプトを使用して、すべてのリポジトリ呼び出しをキャプチャできます。次に、各メソッドで何度も繰り返すのではなく、インターセプターで1回だけ再試行ロジックを記述します。