using System; using System.Data; using Umbraco.Core.Persistence.FaultHandling; namespace Umbraco.Core.Persistence { /// /// Provides a set of extension methods adding retry capabilities into the standard implementation, which is used in PetaPoco. /// public static class PetaPocoCommandExtensions { #region ExecuteNonQueryWithRetry method implementations /// /// Executes a Transact-SQL statement against the connection and returns the number of rows affected. Uses the default retry policy when executing the command. /// /// The command object that is required as per extension method declaration. /// The number of rows affected. public static int ExecuteNonQueryWithRetry(this IDbCommand command) { var connectionString = command.Connection.ConnectionString ?? string.Empty; return ExecuteNonQueryWithRetry(command, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); } /// /// Executes a Transact-SQL statement against the connection and returns the number of rows affected. Uses the specified retry policy when executing the command. /// /// The command object that is required as per extension method declaration. /// The retry policy defining whether to retry a command if a connection fails while executing the command. /// The number of rows affected. public static int ExecuteNonQueryWithRetry(this IDbCommand command, RetryPolicy retryPolicy) { var connectionString = command.Connection.ConnectionString ?? string.Empty; return ExecuteNonQueryWithRetry(command, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); } /// /// Executes a Transact-SQL statement against the connection and returns the number of rows affected. Uses the specified retry policy when executing the command. /// Uses a separate specified retry policy when establishing a connection. /// /// The command object that is required as per extension method declaration. /// The command retry policy defining whether to retry a command if it fails while executing. /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. /// The number of rows affected. public static int ExecuteNonQueryWithRetry(this IDbCommand command, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) { //GuardConnectionIsNotNull(command); // Check if retry policy was specified, if not, use the default retry policy. return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => { var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); try { return command.ExecuteNonQuery(); } finally { if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) { //Connection is closed in PetaPoco, so no need to do it here (?) //command.Connection.Close(); } } }); } #endregion #region ExecuteReaderWithRetry method implementations /// /// Sends the specified command to the connection and builds a SqlDataReader object containing the results. /// Uses the default retry policy when executing the command. /// /// The command object that is required as per extension method declaration. /// A System.Data.IDataReader object. public static IDataReader ExecuteReaderWithRetry(this IDbCommand command) { var connectionString = command.Connection.ConnectionString ?? string.Empty; return ExecuteReaderWithRetry(command, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); } /// /// Sends the specified command to the connection and builds a SqlDataReader object containing the results. /// Uses the specified retry policy when executing the command. /// /// The command object that is required as per extension method declaration. /// The retry policy defining whether to retry a command if a connection fails while executing the command. /// A System.Data.IDataReader object. public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, RetryPolicy retryPolicy) { var connectionString = command.Connection.ConnectionString ?? string.Empty; return ExecuteReaderWithRetry(command, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); } /// /// Sends the specified command to the connection and builds a SqlDataReader object containing the results. /// Uses the specified retry policy when executing the command. Uses a separate specified retry policy when /// establishing a connection. /// /// The command object that is required as per extension method declaration. /// The command retry policy defining whether to retry a command if it fails while executing. /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. /// A System.Data.IDataReader object. public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) { //GuardConnectionIsNotNull(command); // Check if retry policy was specified, if not, use the default retry policy. return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => { var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); try { return command.ExecuteReader(); } catch (Exception) { if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) { //command.Connection.Close(); } throw; } }); } /// /// Sends the specified command to the connection and builds a SqlDataReader object using one of the /// CommandBehavior values. Uses the default retry policy when executing the command. /// /// The command object that is required as per extension method declaration. /// One of the System.Data.CommandBehavior values. /// A System.Data.IDataReader object. public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, CommandBehavior behavior) { var connectionString = command.Connection.ConnectionString ?? string.Empty; return ExecuteReaderWithRetry(command, behavior, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); } /// /// Sends the specified command to the connection and builds a SqlDataReader object using one of the /// CommandBehavior values. Uses the specified retry policy when executing the command. /// /// The command object that is required as per extension method declaration. /// One of the System.Data.CommandBehavior values. /// The retry policy defining whether to retry a command if a connection fails while executing the command. /// A System.Data.SqlClient.SqlDataReader object. public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, CommandBehavior behavior, RetryPolicy retryPolicy) { var connectionString = command.Connection.ConnectionString ?? string.Empty; return ExecuteReaderWithRetry(command, behavior, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); } /// /// Sends the specified command to the connection and builds a SqlDataReader object using one of the /// CommandBehavior values. Uses the specified retry policy when executing the command. /// Uses a separate specified retry policy when establishing a connection. /// /// The command object that is required as per extension method declaration. /// One of the System.Data.CommandBehavior values. /// The command retry policy defining whether to retry a command if it fails while executing. /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. /// A System.Data.IDataReader object. public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, CommandBehavior behavior, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) { //GuardConnectionIsNotNull(command); // Check if retry policy was specified, if not, use the default retry policy. return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => { var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); try { return command.ExecuteReader(behavior); } catch (Exception) { if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) { //command.Connection.Close(); } throw; } }); } #endregion #region ExecuteScalarWithRetry method implementations /// /// Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored. /// Uses the default retry policy when executing the command. /// /// The command object that is required as per extension method declaration. /// The first column of the first row in the result set, or a null reference if the result set is empty. Returns a maximum of 2033 characters. public static object ExecuteScalarWithRetry(this IDbCommand command) { var connectionString = command.Connection.ConnectionString ?? string.Empty; return ExecuteScalarWithRetry(command, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); } /// /// Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored. /// Uses the specified retry policy when executing the command. /// /// The command object that is required as per extension method declaration. /// The retry policy defining whether to retry a command if a connection fails while executing the command. /// The first column of the first row in the result set, or a null reference if the result set is empty. Returns a maximum of 2033 characters. public static object ExecuteScalarWithRetry(this IDbCommand command, RetryPolicy retryPolicy) { var connectionString = command.Connection.ConnectionString ?? string.Empty; return ExecuteScalarWithRetry(command, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); } /// /// Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored. /// Uses the specified retry policy when executing the command. Uses a separate specified retry policy when establishing a connection. /// /// The command object that is required as per extension method declaration. /// The command retry policy defining whether to retry a command if it fails while executing. /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. /// The first column of the first row in the result set, or a null reference if the result set is empty. Returns a maximum of 2033 characters. public static object ExecuteScalarWithRetry(this IDbCommand command, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) { //GuardConnectionIsNotNull(command); // Check if retry policy was specified, if not, use the default retry policy. return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => { var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); try { return command.ExecuteScalar(); } finally { if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) { //Connection is closed in PetaPoco, so no need to do it here (?) //command.Connection.Close(); } } }); } #endregion /// /// Ensure a valid connection in case a connection hasn't been opened by PetaPoco (which shouldn't be possible by the way). /// /// /// /// private static bool EnsureValidConnection(IDbCommand command, RetryPolicy retryPolicy) { if (command != null) { //GuardConnectionIsNotNull(command); // Verify whether or not the connection is valid and is open. This code may be retried therefore // it is important to ensure that a connection is re-established should it have previously failed. if (command.Connection.State != ConnectionState.Open) { // Attempt to open the connection using the retry policy that matches the policy for SQL commands. command.Connection.OpenWithRetry(retryPolicy); return true; } } return false; } } }