public void Verify_that_2627_unique_constraint_is_parsed_correctly()
        {
            var message =
                "Violation of UNIQUE KEY constraint 'UQ_TableName_ColumnName'. Cannot insert duplicate key in object 'dbo.TableName'. The duplicate key value is (blablabla).";

            var violation = SqlUniqueKeyViolation.Parse(SqlErrorCodes.UniqueConstraint, message);

            Assert.AreEqual("TableName", violation.TableName, "TableName incorrect.");
            Assert.AreEqual("ColumnName", violation.KeyName, "KeyName incorrect.");
            Assert.AreEqual("blablabla", violation.DuplicateKeyValue, "DuplicateKeyValue incorrect.");
        }
        public void Verify_that_2601_unique_index_is_parsed_correctly()
        {
            var message =
                "Cannot insert duplicate key row in object 'dbo.TableName' with unique index 'IDX_TableName_ColumnName'. The duplicate key value is (blablabla).";

            var violation = SqlUniqueKeyViolation.Parse(SqlErrorCodes.UniqueIndexConstraint, message);

            Assert.AreEqual("TableName", violation.TableName, "TableName incorrect.");
            Assert.AreEqual("ColumnName", violation.KeyName, "KeyName incorrect.");
            Assert.AreEqual("blablabla", violation.DuplicateKeyValue, "DuplicateKeyValue incorrect.");
        }
        public void Verify_that_2601_unique_index_is_parsed_correctly_when_underscore_in_key_value()
        {
            var message =
                "Cannot insert duplicate key row in object 'dbo.TableName' with unique index 'idx_TableName_ColumnName'. The duplicate key value is (+«ÌSµ…p¹¸'Ä£M_t¥G).";

            var violation = SqlUniqueKeyViolation.Parse(SqlErrorCodes.UniqueIndexConstraint, message);

            Assert.AreEqual("TableName", violation.TableName, "TableName incorrect.");
            Assert.AreEqual("ColumnName", violation.KeyName, "KeyName incorrect.");
            Assert.AreEqual("+«ÌSµ…p¹¸'Ä£M_t¥G", violation.DuplicateKeyValue, "DuplicateKeyValue incorrect.");
        }
        /// <summary>
        /// Tries to execute the guarded code.
        /// </summary>
        /// <returns>The return value of type T, on success.</returns>
        /// <exception cref="ProviderInaccessibleException">On unknown errors and when the circuit continues to trip.</exception>
        /// <exception cref="DuplicateKeyException">When duplicate keys are detected.</exception>
        /// <exception cref="DataUpdatedException">When an optimistic concurrency conflict is detected.</exception>
        /// <exception cref="ErrorHandling.TimeoutException">When an action timed out.</exception>
        /// <exception cref="DeadlockedException">When a deadlock is detected.</exception>
        /// <remarks>Only two exceptions need to be catched: ProviderInaccessibleException and RecoverableException, since
        /// DuplicateKeyException, DataUpdatedException, TimeoutException and DeadlockedException all inherit from
        /// RecoverableException.</remarks>
        public async Task <TReturn> Execute()
        {
            var         coolOffPeriod         = retrySettings.CoolOffPeriod;
            int         tryNumber             = 0;
            Func <bool> retryThresholdReached = () => tryNumber >= retrySettings.RetryCount;
            var         exceptionsCaught      = new List <Exception>();

            while (!retryThresholdReached())
            {
                tryNumber++;

                try
                {
                    return(await awaitableAction());
                }
                catch (InvalidOperationException exception)
                {
                    // InvalidOperationException most likely suggests that we tried to execute
                    // when the curcuit breaker was in an invalid state.
                    exceptionsCaught.Add(exception);
                    if (retryThresholdReached())
                    {
                        throw new ProviderInaccessibleException(exceptionsCaught);
                    }
                }
                catch (SqlException sqlException)
                {
                    if (sqlException.Number == SqlErrorCodes.ForeignKeyConstraint)
                    {
                        // This is an error, which is not recoverable here, so throw a new exception instead of retrying.
                        var violation = SqlForeignKeyViolation.Parse(sqlException.Number, sqlException.Message);
                        throw new ForeignKeyException(violation.LocalTable, violation.ForeignTable, violation.ForeignColumn, sqlException.Message, sqlException);
                    }

                    if (sqlException.Number == SqlErrorCodes.UniqueConstraint || sqlException.Number == SqlErrorCodes.UniqueIndexConstraint)
                    {
                        // This is a duplicate key error, which is not recoverable here, so throw a new exception instead of retrying.
                        var violation = SqlUniqueKeyViolation.Parse(sqlException.Number, sqlException.Message);
                        throw new DuplicateKeyException(violation.TableName, violation.KeyName, violation.DuplicateKeyValue, sqlException.Message, sqlException);
                    }

                    if (sqlException.Number == SqlErrorCodes.Deadlock)
                    {
                        // This is an error, which may be recoverable, so only throw if no more retries are left.
                        if (retryThresholdReached())
                        {
                            throw new DeadlockedException(sqlException);
                        }
                    }

                    if (sqlException.Number == SqlErrorCodes.Timeout)
                    {
                        // This is an error, which may be recoverable, so only throw if no more retries are left.
                        if (retryThresholdReached())
                        {
                            throw new TimeoutException(sqlException);
                        }
                    }

                    if (sqlException.Number == SqlErrorCodes.StringOrBinaryDataTruncation)
                    {
                        throw new TruncatedDataException("Data truncated.", sqlException);
                    }

                    if (retryThresholdReached())
                    {
                        // Something is seriously wrong when we get to here. It is not recoverable.
                        throw new ProviderInaccessibleException(sqlException);
                    }
                }
                catch (TransactionAbortedException exception)
                {
                    exceptionsCaught.Add(exception);
                    // Something is seriously wrong when we get this exception. It is not recoverable.
                    throw new ProviderInaccessibleException(methodDescriptor(), exceptionsCaught);
                }
                catch (Exception exception)
                {
                    exceptionsCaught.Add(exception);
                    // A generic exception may come from the guarded code. But since we have no idea what went
                    // wrong, we must assume that the error is not recoverable.
                    throw new ProviderInaccessibleException(methodDescriptor(), exceptionsCaught);
                }

                await Task.Delay(retrySettings.CoolOffPeriod);
            }

            // We have exceeded the retryCount so report that we cannot access the provider
            throw new ProviderInaccessibleException("Retry count exceeded. " + methodDescriptor(), exceptionsCaught);
        }