using System; using System.Collections.Generic; using System.Linq; using Microsoft.Data.SqlClient; // ReSharper disable UnusedMember.Global namespace Tapeti.Flow.SQL { internal static class SqlExceptionHelper { // 2601: Cannot insert duplicate key row in object '%.*ls' with unique index '%.*ls'. The duplicate key value is %ls. // 2627: Violation of %ls constraint '%.*ls'. Cannot insert duplicate key in object '%.*ls'. The duplicate key value is %ls. public static bool IsDuplicateKey(SqlException e) { return e is { Number: 2601 or 2627 }; } public static bool IsTransientError(Exception e) { switch (e) { case TimeoutException: return true; case not null: { var sqlExceptions = ExtractSqlExceptions(e); return sqlExceptions.Select(UnwrapSqlErrors).Any(IsRecoverableSQLError); } default: return false; } } /// /// Extracts alls SqlExceptions from the main and inner or aggregate exceptions /// public static IEnumerable ExtractSqlExceptions(Exception exception) { var exceptionHead = exception; while (exceptionHead != null) { switch (exceptionHead) { case AggregateException aggregateException: foreach (var innerException in aggregateException.InnerExceptions) { foreach (var extractedSqlException in ExtractSqlExceptions(innerException)) yield return extractedSqlException; } break; case SqlException sqlException: yield return sqlException; break; } exceptionHead = exceptionHead.InnerException; } } /// /// Goes through all errors in a SqlException, and any InnerException that is also a SqlException. /// public static IEnumerable UnwrapSqlErrors(SqlException exception) { var exceptionHead = exception; while (exceptionHead != null) { foreach (SqlError error in exceptionHead.Errors) yield return error; exceptionHead = exceptionHead.InnerException as SqlException; } } // Mostly borrowed from EF: // https://github.com/aspnet/EntityFrameworkCore/blob/d8b7ebbfabff3d2e8560c24b1ff14d1f4244ca6a/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs private static bool IsRecoverableSQLError(IEnumerable sqlErrors) { foreach (var err in sqlErrors) { switch (err.Number) { // This exception can be thrown even if the operation completed succesfully, so it's safer to let the application fail. // DBNETLIB Error Code: -2 // Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. The statement has been terminated. // In onze situatie mag elke repository operation nogmaals uitgevoerd worden dus gaan we deze retry-en, ook al vind EF van niet. case -2: // EF doesn't recognize this one for some reason, but it occurs when I stop the SQL Server service case 2: // I had this occur once as well, even though all credentials were valid, so assume it's transient as well case 4060: // SQL Error Code: 49920 // Cannot process request. Too many operations in progress for subscription "%ld". // The service is busy processing multiple requests for this subscription. // Requests are currently blocked for resource optimization. Query sys.dm_operation_status for operation status. // Wait until pending requests are complete or delete one of your pending requests and retry your request later. case 49920: // SQL Error Code: 49919 // Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". // The service is busy processing multiple create or update requests for your subscription or server. // Requests are currently blocked for resource optimization. Query sys.dm_operation_status for pending operations. // Wait till pending create or update requests are complete or delete one of your pending requests and // retry your request later. case 49919: // SQL Error Code: 49918 // Cannot process request. Not enough resources to process request. // The service is currently busy.Please retry the request later. case 49918: // SQL Error Code: 41839 // Transaction exceeded the maximum number of commit dependencies. case 41839: // SQL Error Code: 41325 // The current transaction failed to commit due to a serializable validation failure. case 41325: // SQL Error Code: 41305 // The current transaction failed to commit due to a repeatable read validation failure. case 41305: // SQL Error Code: 41302 // The current transaction attempted to update a record that has been updated since the transaction started. case 41302: // SQL Error Code: 41301 // Dependency failure: a dependency was taken on another transaction that later failed to commit. case 41301: // SQL Error Code: 40613 // Database XXXX on server YYYY is not currently available. Please retry the connection later. // If the problem persists, contact customer support, and provide them the session tracing ID of ZZZZZ. case 40613: // SQL Error Code: 40501 // The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded). case 40501: // SQL Error Code: 40197 // The service has encountered an error processing your request. Please try again. case 40197: // SQL Error Code: 10929 // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. // However, the server is currently too busy to support requests greater than %d for this database. // For more information, see http://go.microsoft.com/fwlink/?LinkId=267637. Otherwise, please try again. case 10929: // SQL Error Code: 10928 // Resource ID: %d. The %s limit for the database is %d and has been reached. For more information, // see http://go.microsoft.com/fwlink/?LinkId=267637. case 10928: // SQL Error Code: 10060 // A network-related or instance-specific error occurred while establishing a connection to SQL Server. // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed // because the connected party did not properly respond after a period of time, or established connection failed // because connected host has failed to respond.)"} case 10060: // SQL Error Code: 10054 // A transport-level error has occurred when sending the request to the server. // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) case 10054: // SQL Error Code: 10053 // A transport-level error has occurred when receiving results from the server. // An established connection was aborted by the software in your host machine. case 10053: // SQL Error Code: 1205 // Deadlock case 1205: // SQL Error Code: 233 // The client was unable to establish a connection because of an error during connection initialization process before login. // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; // the server was too busy to accept new connections; or there was a resource limitation (insufficient memory or maximum // allowed connections) on the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by // the remote host.) case 233: // SQL Error Code: 121 // The semaphore timeout period has expired case 121: // SQL Error Code: 64 // A connection was successfully established with the server, but then an error occurred during the login process. // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) case 64: // DBNETLIB Error Code: 20 // The instance of SQL Server you attempted to connect to does not support encryption. case 20: return true; } } return false; } } }