From 81b268c40431c09c3fb4d431849920f96faba4d2 Mon Sep 17 00:00:00 2001 From: Adam McCoy Date: Fri, 30 Jun 2023 16:27:03 +1000 Subject: [PATCH 1/2] Refactor retry re-connection methods --- .../Transient/DbCommandExtensions.cs | 58 ++++--------------- source/Nevermore/Transient/RetryUtil.cs | 45 ++++++++++++++ 2 files changed, 57 insertions(+), 46 deletions(-) create mode 100644 source/Nevermore/Transient/RetryUtil.cs diff --git a/source/Nevermore/Transient/DbCommandExtensions.cs b/source/Nevermore/Transient/DbCommandExtensions.cs index 780e004e..7e808d54 100644 --- a/source/Nevermore/Transient/DbCommandExtensions.cs +++ b/source/Nevermore/Transient/DbCommandExtensions.cs @@ -10,11 +10,11 @@ internal static class DbCommandExtensions { public static int ExecuteNonQueryWithRetry(this DbCommand command, RetryPolicy commandRetryPolicy, RetryPolicy connectionRetryPolicy = null, string operationName = "ExecuteNonQuery") { - GuardConnectionIsNotNull(command); + RetryUtil.GuardConnectionIsNotNull(command.Connection); var effectiveCommandRetryPolicy = (commandRetryPolicy ?? RetryPolicy.NoRetry).LoggingRetries(operationName); return effectiveCommandRetryPolicy.ExecuteAction(() => { - var weOwnTheConnectionLifetime = EnsureValidConnection(command, connectionRetryPolicy); + var weOwnTheConnectionLifetime = RetryUtil.EnsureValidConnection(command.Connection, connectionRetryPolicy); try { return command.ExecuteNonQuery(); @@ -29,11 +29,11 @@ public static int ExecuteNonQueryWithRetry(this DbCommand command, RetryPolicy c public static Task ExecuteNonQueryWithRetryAsync(this DbCommand command, RetryPolicy commandRetryPolicy, RetryPolicy connectionRetryPolicy = null, string operationName = "ExecuteNonQueryAsync", CancellationToken cancellationToken = default) { - GuardConnectionIsNotNull(command); + RetryUtil.GuardConnectionIsNotNull(command.Connection); var effectiveCommandRetryPolicy = (commandRetryPolicy ?? RetryPolicy.NoRetry).LoggingRetries(operationName); return effectiveCommandRetryPolicy.ExecuteAction(async () => { - var weOwnTheConnectionLifetime = await EnsureValidConnectionAsync(command, connectionRetryPolicy, cancellationToken).ConfigureAwait(false); + var weOwnTheConnectionLifetime = await RetryUtil.EnsureValidConnectionAsync(command.Connection, connectionRetryPolicy, cancellationToken).ConfigureAwait(false); try { return await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); @@ -48,11 +48,11 @@ public static Task ExecuteNonQueryWithRetryAsync(this DbCommand command, Re public static DbDataReader ExecuteReaderWithRetry(this DbCommand command, RetryPolicy commandRetryPolicy, CommandBehavior behavior = CommandBehavior.Default, RetryPolicy connectionRetryPolicy = null, string operationName = "ExecuteReader") { - GuardConnectionIsNotNull(command); + RetryUtil.GuardConnectionIsNotNull(command.Connection); var effectiveCommandRetryPolicy = (commandRetryPolicy ?? RetryPolicy.NoRetry).LoggingRetries(operationName); return effectiveCommandRetryPolicy.ExecuteAction(() => { - var weOwnTheConnectionLifetime = EnsureValidConnection(command, connectionRetryPolicy); + var weOwnTheConnectionLifetime = RetryUtil.EnsureValidConnection(command.Connection, connectionRetryPolicy); try { return command.ExecuteReader(behavior); @@ -69,12 +69,12 @@ public static DbDataReader ExecuteReaderWithRetry(this DbCommand command, RetryP public static async Task ExecuteReaderWithRetryAsync(this DbCommand command, RetryPolicy commandRetryPolicy, CommandBehavior commandBehavior, CancellationToken cancellationToken, RetryPolicy connectionRetryPolicy = null, string operationName = "ExecuteReader") { - GuardConnectionIsNotNull(command); + RetryUtil.GuardConnectionIsNotNull(command.Connection); var effectiveCommandRetryPolicy = (commandRetryPolicy ?? RetryPolicy.NoRetry).LoggingRetries(operationName); return await effectiveCommandRetryPolicy.ExecuteActionAsync(async () => { - var weOwnTheConnectionLifetime = await EnsureValidConnectionAsync(command, connectionRetryPolicy, cancellationToken).ConfigureAwait(false); + var weOwnTheConnectionLifetime = await RetryUtil.EnsureValidConnectionAsync(command.Connection, connectionRetryPolicy, cancellationToken).ConfigureAwait(false); try { return await command.ExecuteReaderAsync(commandBehavior, cancellationToken).ConfigureAwait(false); @@ -91,11 +91,11 @@ public static async Task ExecuteReaderWithRetryAsync(this DbComman public static object ExecuteScalarWithRetry(this DbCommand command, RetryPolicy commandRetryPolicy, RetryPolicy connectionRetryPolicy = null, string operationName = "ExecuteScalar") { - GuardConnectionIsNotNull(command); + RetryUtil.GuardConnectionIsNotNull(command.Connection); var effectiveCommandRetryPolicy = (commandRetryPolicy ?? RetryPolicy.NoRetry).LoggingRetries(operationName); return effectiveCommandRetryPolicy.ExecuteAction(() => { - var weOwnTheConnectionLifetime = EnsureValidConnection(command, connectionRetryPolicy); + var weOwnTheConnectionLifetime = RetryUtil.EnsureValidConnection(command.Connection, connectionRetryPolicy); try { return command.ExecuteScalar(); @@ -110,11 +110,11 @@ public static object ExecuteScalarWithRetry(this DbCommand command, RetryPolicy public static async Task ExecuteScalarWithRetryAsync(this DbCommand command, RetryPolicy commandRetryPolicy, RetryPolicy connectionRetryPolicy = null, string operationName = "ExecuteScalar", CancellationToken cancellationToken = default) { - GuardConnectionIsNotNull(command); + RetryUtil.GuardConnectionIsNotNull(command.Connection); var effectiveCommandRetryPolicy = (commandRetryPolicy ?? RetryManager.Instance.GetDefaultSqlCommandRetryPolicy()).LoggingRetries(operationName); return await effectiveCommandRetryPolicy.ExecuteActionAsync(async () => { - var weOwnTheConnectionLifetime = await EnsureValidConnectionAsync(command, connectionRetryPolicy, cancellationToken).ConfigureAwait(false); + var weOwnTheConnectionLifetime = await RetryUtil.EnsureValidConnectionAsync(command.Connection, connectionRetryPolicy, cancellationToken).ConfigureAwait(false); try { return await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); @@ -126,39 +126,5 @@ public static async Task ExecuteScalarWithRetryAsync(this DbCommand comm } }).ConfigureAwait(false); } - - static void GuardConnectionIsNotNull(DbCommand command) - { - if (command.Connection == null) - throw new InvalidOperationException("Connection property has not been initialized."); - } - - /// - /// Ensures the command either has an existing open connection, or we will open one for it. - /// - /// True if we opened the connection (indicating we own its lifetime), False if the connection was already open (indicating someone else owns its lifetime) - static bool EnsureValidConnection(DbCommand command, RetryPolicy retryPolicy) - { - if (command == null) return false; - - GuardConnectionIsNotNull(command); - - if (command.Connection.State == ConnectionState.Open) return false; - - command.Connection.OpenWithRetry(retryPolicy); - return true; - } - - static async Task EnsureValidConnectionAsync(DbCommand command, RetryPolicy retryPolicy, CancellationToken cancellationToken) - { - if (command == null) return false; - - GuardConnectionIsNotNull(command); - - if (command.Connection.State == ConnectionState.Open) return false; - - await command.Connection.OpenWithRetryAsync(retryPolicy, cancellationToken).ConfigureAwait(false); - return true; - } } } \ No newline at end of file diff --git a/source/Nevermore/Transient/RetryUtil.cs b/source/Nevermore/Transient/RetryUtil.cs new file mode 100644 index 00000000..23f62580 --- /dev/null +++ b/source/Nevermore/Transient/RetryUtil.cs @@ -0,0 +1,45 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; + +namespace Nevermore.Transient +{ + internal static class RetryUtil + { + public static void GuardConnectionIsNotNull(DbConnection connection) + { + if (connection == null) + throw new InvalidOperationException("Connection property has not been initialized."); + } + + /// + /// Ensures the command either has an existing open connection, or we will open one for it. + /// + /// True if we opened the connection (indicating we own its lifetime), False if the connection was already open (indicating someone else owns its lifetime) + public static bool EnsureValidConnection(DbConnection connection, RetryPolicy retryPolicy) + { + if (connection == null) return false; + + GuardConnectionIsNotNull(connection); + + if (connection.State == ConnectionState.Open) return false; + + connection.OpenWithRetry(retryPolicy); + return true; + } + + public static async Task EnsureValidConnectionAsync(DbConnection connection, RetryPolicy retryPolicy, CancellationToken cancellationToken) + { + if (connection == null) return false; + + GuardConnectionIsNotNull(connection); + + if (connection.State == ConnectionState.Open) return false; + + await connection.OpenWithRetryAsync(retryPolicy, cancellationToken).ConfigureAwait(false); + return true; + } + } +} \ No newline at end of file From 1b235dc807a0af7dbf6f431f223ed0f450cef8c7 Mon Sep 17 00:00:00 2001 From: Adam McCoy Date: Fri, 30 Jun 2023 16:39:16 +1000 Subject: [PATCH 2/2] Re-establish connection if necessary --- source/Nevermore/Transient/TransactionExtensions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/Nevermore/Transient/TransactionExtensions.cs b/source/Nevermore/Transient/TransactionExtensions.cs index 316f335e..5c7e12b4 100644 --- a/source/Nevermore/Transient/TransactionExtensions.cs +++ b/source/Nevermore/Transient/TransactionExtensions.cs @@ -2,7 +2,6 @@ using System.Data.Common; using Microsoft.Data.SqlClient; - namespace Nevermore.Transient { public static class TransactionExtensions @@ -14,7 +13,11 @@ public static DbTransaction BeginTransactionWithRetry(this SqlConnection connect public static DbTransaction BeginTransactionWithRetry(this SqlConnection connection, IsolationLevel isolationLevel, string sqlServerTransactionName, RetryPolicy retryPolicy) { - return (retryPolicy ?? RetryPolicy.NoRetry).LoggingRetries("Beginning Database Transaction").ExecuteAction(() => connection.BeginTransaction(isolationLevel, sqlServerTransactionName)); + return (retryPolicy ?? RetryPolicy.NoRetry).LoggingRetries("Beginning Database Transaction").ExecuteAction(() => + { + RetryUtil.EnsureValidConnection(connection, retryPolicy); + return connection.BeginTransaction(isolationLevel, sqlServerTransactionName); + }); } } } \ No newline at end of file