복붙노트

[SQL] 하지 액세스 SqlTransaction 객체는 catch 블록에서 롤백 할 수

SQL

하지 액세스 SqlTransaction 객체는 catch 블록에서 롤백 할 수

나는 문제가있어, 모든 기사 또는 예 나는 그것에 대해 걱정하지 보인다 발견했다.

나는 거래에서 일부 데이터베이스 작업을 수행 할. 내가 뭘 원하는 대부분의 예와 매우 유사합니다 :

using (SqlConnection Conn = new SqlConnection(_ConnectionString))
{
    try
    {
        Conn.Open();
        SqlTransaction Trans = Conn.BeginTransaction();

        using (SqlCommand Com = new SqlCommand(ComText, Conn))
        {
            /* DB work */
        }
    }
    catch (Exception Ex)
    {
        Trans.Rollback();
        return -1;
    }
}

그러나 문제는 SqlTransaction 트랜스는 try 블록 내에서 선언 된 것입니다. 그래서 캐치 () 블록 accessable 한하지 않습니다. 대부분의 예는 try 블록 전에 Conn.Open ()와 Conn.BeginTransaction ()를 수행하지만 모두가 여러 예외를 던질 수 있기 때문에 즉, 조금 위험한 생각합니다.

암 I의 잘못은, 또는 대부분의 사람들은 이러한 위험을 무시합니까? 가장 좋은 방법은 예외가 발생하는 경우, 롤백 할 수 있도록 무엇입니까?

해결법

  1. ==============================

    1.

    using (var Conn = new SqlConnection(_ConnectionString))
    {
        SqlTransaction trans = null;
        try
        {
            Conn.Open();
            trans = Conn.BeginTransaction();
    
            using (SqlCommand Com = new SqlCommand(ComText, Conn, trans))
            {
                /* DB work */
            }
            trans.Commit();
        }
        catch (Exception Ex)
        {
            if (trans != null) trans.Rollback();
            return -1;
        }
    }
    

    또는 당신도 깨끗하고 쉽게 이동이를 사용할 수 있습니다 :

    using (var Conn = new SqlConnection(_ConnectionString))
    {
        try
        {
            Conn.Open();
            using (var ts = new System.Transactions.TransactionScope())
            {
                using (SqlCommand Com = new SqlCommand(ComText, Conn))
                {
                    /* DB work */
                }
                ts.Complete();
            }
        }
        catch (Exception Ex)
        {     
            return -1;
        }
    }
    
  2. ==============================

    2.나는 그리, 유형을 입력하고 null로 변수를 설정 좋아한다 :

    나는 그리, 유형을 입력하고 null로 변수를 설정 좋아한다 :

    try
    {
        using (var conn = new SqlConnection(/* connection string or whatever */))
        {
            conn.Open();
    
            using (var trans = conn.BeginTransaction())
            {
                try
                {
                    using (var cmd = conn.CreateCommand())
                    {
                        cmd.Transaction = trans;
                        /* setup command type, text */
                        /* execute command */
                    }
    
                    trans.Commit();
                }
                catch (Exception ex)
                {
                    trans.Rollback();
                    /* log exception and the fact that rollback succeeded */
                }
            }
        }
    }
    catch (Exception ex)
    {
        /* log or whatever */
    }
    

    당신이 MySQL을하거나 다른 업체로 전환하고자한다면, 당신은 단 1 줄을 수정해야 할 것이다.

  3. ==============================

    3.이것을 사용

    이것을 사용

    using (SqlConnection Conn = new SqlConnection(_ConnectionString))
    {
        SqlTransaction Trans = null;
        try
        {
            Conn.Open();
            Trans = Conn.BeginTransaction();
    
            using (SqlCommand Com = new SqlCommand(ComText, Conn))
            {
                /* DB work */
            }
        }
        catch (Exception Ex)
        {
            if (Trans != null)
                Trans.Rollback();
            return -1;
        }
    }
    

    BTW - 당신은 성공적으로 처리하는 경우이를 저지하지 않았다

  4. ==============================

    4.

    using (SqlConnection Conn = new SqlConnection(_ConnectionString))
    {
        try
        {
            Conn.Open();
            SqlTransaction Trans = Conn.BeginTransaction();
    
            try 
            {
                using (SqlCommand Com = new SqlCommand(ComText, Conn))
                {
                    /* DB work */
                }
            }
            catch (Exception TransEx)
            {
                Trans.Rollback();
                return -1;
            }
        }
        catch (Exception Ex)
        {
            return -1;
        }
    }
    
  5. ==============================

    5.마이크로 소프트 샘플은이 MSDN의 링크를 참조 시도 / 캐치의 트랜스 외부를 시작 놓습니다. 내가 들어 BeginTransaction 방법 중 하나 예외를 던지거나 트랜잭션을 시작하지만 결코 모두 (문서는 이것이 불가능 말을하지 않지만)한다고 가정합니다.

    마이크로 소프트 샘플은이 MSDN의 링크를 참조 시도 / 캐치의 트랜스 외부를 시작 놓습니다. 내가 들어 BeginTransaction 방법 중 하나 예외를 던지거나 트랜잭션을 시작하지만 결코 모두 (문서는 이것이 불가능 말을하지 않지만)한다고 가정합니다.

    그러나, 당신은 더 나은 당신을 위해의 많은 (그렇지 않은) 무거운를 관리하여 TransactionScope를 사용 할 수있다 :이 링크

  6. ==============================

    6.

    SqlConnection conn = null;
    SqlTransaction trans = null;
    
    try
    {
       conn = new SqlConnection(_ConnectionString);
       conn.Open();
       trans = conn.BeginTransaction();
       /*
        * DB WORK
        */
       trans.Commit();
    }
    catch (Exception ex)
    {
       if (trans != null)
       {
          trans.Rollback();
       }
       return -1;
    }
    finally
    {
       if (conn != null)
       {
          conn.Close();
       }
    }
    
  7. ==============================

    7.나는이 질문에게 2018의 처음 끝을 발견했을 때 나는 대답을 투표 한 다음 상단에 버그가있을 수 있다고 생각하지 못했지만, 거기 간다. 내가 먼저 간단하게 대답을 주석에 대해 생각하지만 다시 난 내 자신의 참조 내 주장을 백업하고 싶었다. 그리고 테스트 I 않았다 (닷넷 프레임 워크 4.6.1 및 닷넷 코어 2.1을 기반으로.)

    나는이 질문에게 2018의 처음 끝을 발견했을 때 나는 대답을 투표 한 다음 상단에 버그가있을 수 있다고 생각하지 못했지만, 거기 간다. 내가 먼저 간단하게 대답을 주석에 대해 생각하지만 다시 난 내 자신의 참조 내 주장을 백업하고 싶었다. 그리고 테스트 I 않았다 (닷넷 프레임 워크 4.6.1 및 닷넷 코어 2.1을 기반으로.)

    영업의 제약 조건을 감안할 때, 트랜잭션은 이미 다른 답변에서 언급 한 2 개의 다른 구현에 우리를 잎 연결 내에서 선언되어야한다 :

    TransactionScope에 사용

    using (SqlConnection conn = new SqlConnection(conn2))
    {
        try
        {
            conn.Open();
            using (TransactionScope ts = new TransactionScope())
            {
                conn.EnlistTransaction(Transaction.Current);
                using (SqlCommand command = new SqlCommand(query, conn))
                {
                    command.ExecuteNonQuery();
                    //TESTING: throw new System.InvalidOperationException("Something bad happened.");
                }
                ts.Complete();
            }
        }
        catch (Exception)
        {
            throw;
        }
    }
    

    SqlTransaction을 사용하여

    using (SqlConnection conn = new SqlConnection(conn3))
    {
        try
        {
            conn.Open();
            using (SqlTransaction ts = conn.BeginTransaction())
            {
                using (SqlCommand command = new SqlCommand(query, conn, ts))
                {
                    command.ExecuteNonQuery();
                    //TESTING: throw new System.InvalidOperationException("Something bad happened.");
                }
                ts.Commit();
            }
        }
        catch (Exception)
        {
            throw;
        }
    }
    

    대신 당신이 conn.EnlistTransaction에 명시 적으로 입대해야하는도록 SqlConnection 내에서 TransactionScope에를 선언 할 때 연결 개체가 자동으로 트랜잭션에 입대되지 않는다는 것을 알고 있어야합니다 (Transaction.Current);

    테스트 및 증명 나는 SQL Server 데이터베이스에 간단한 테이블을 준비했습니다 :

    SELECT * FROM [staging].[TestTable]
    
    Column1
    -----------
    1
    

    다음과 같이 .NET에서 업데이트 쿼리는 다음과 같습니다

    string query = @"UPDATE staging.TestTable
                        SET Column1 = 2";
    

    그리고 command.ExecuteNonQuery () 예외가 발생 직후 :

    command.ExecuteNonQuery();
    throw new System.InvalidOperationException("Something bad happened.");
    

    여기 참조에 대한 전체 예입니다 :

    string query = @"UPDATE staging.TestTable
                        SET Column1 = 2";
    
    using (SqlConnection conn = new SqlConnection(conn2))
    {
        try
        {
            conn.Open();
            using (TransactionScope ts = new TransactionScope())
            {
                conn.EnlistTransaction(Transaction.Current);
                using (SqlCommand command = new SqlCommand(query, conn))
                {
                    command.ExecuteNonQuery();
                    throw new System.InvalidOperationException("Something bad happened.");
                }
                ts.Complete();
            }
        }
        catch (Exception)
        {
            throw;
        }
    }
    

    테스트가 실행되면 그것을 TransactionScope에 완료되기 전에 예외를 발생하고 업데이트 테이블 (트랜잭션 롤백) 불변 값 레스트에 적용되지 않는다. 이 모두가 예상하는대로 의도 된 동작입니다.

    Column1
    -----------
    1
    

    우리가 conn.EnlistTransaction (Transaction.Current)와 트랜잭션에서 연결을 입대 잊어 버린 경우 이제 어떻게;?

    예를 재실행하면 다시 예외를 유발하여 실행 흐름 캐치 블록으로 바로 점프한다. ts.Complete ()이기는하지만; 테이블 값이 변경 호출되지 않습니다 :

    Column1
    -----------
    2
    

    트랜잭션 범위가도록 SqlConnection 후에 선언으로 연결이 범위를 인식하지 않고 암시 적으로 소위 주변 트랜잭션에 참여하지 않습니다.

    데이터베이스 바보에 대한 더 깊은 분석

    실행이 command.ExecuteNonQuery () 후 일시 정지하는 경우에도 깊이 파고하려면; 예외가 발생하기 전에 우리는 다음과 같이 데이터베이스 (SQL 서버)에 거래를 조회 할 수 있습니다 :

    SELECT tst.session_id, tat.transaction_id, is_local, open_transaction_count, transaction_begin_time, dtc_state, dtc_status
      FROM sys.dm_tran_session_transactions tst
      LEFT JOIN sys.dm_tran_active_transactions tat
      ON tst.transaction_id = tat.transaction_id
      WHERE tst.session_id IN (SELECT session_id FROM sys.dm_exec_sessions WHERE program_name = 'TransactionScopeTest')
    

    이 연결 문자열에 응용 프로그램 이름 속성을 통해 세션 program_name은을 설정할 수 있습니다를 수행 응용 프로그램 이름 = TransactionScopeTest;

    현재 기존의 트랜잭션은 아래 펼쳐지고 :

    session_id  transaction_id       is_local open_transaction_count transaction_begin_time  dtc_state   dtc_status
    ----------- -------------------- -------- ---------------------- ----------------------- ----------- -----------
    113         6321722              1        1                      2018-11-30 09:09:06.013 0           0
    

    conn.EnlistTransaction (Transaction.Current)없이; 어떤 트랜잭션이 활성 연결에 바인딩되지 않습니다 따라서 변경 트랜잭션 컨텍스트에서 발생하지 않습니다

    session_id  transaction_id       is_local open_transaction_count transaction_begin_time  dtc_state   dtc_status
    ----------- -------------------- -------- ---------------------- ----------------------- ----------- -----------
    

    .NET Framework는 .NET 핵심 대 비고 .NET 코어 I 내 테스트하는 동안 다음과 같은 예외를 건너 왔어요 :

    System.NotSupportedException: 'Enlisting in Ambient transactions is not supported.'
    

    현재 범위가 전이나도록 SqlConnection 후 초기화 여부에 상관없이 TransactionScope의 접근 방식을 지원하지 않는 .NET 코어 (2.1.0)을 보인다.

  8. from https://stackoverflow.com/questions/2912112/cannot-access-sqltransaction-object-to-rollback-in-catch-block by cc-by-sa and MIT license