문제

누군가 (나 이외의 사람)가 다음 코드를 작성하고이를 어셈블리로 컴파일한다고 가정 해 봅시다.

using (SqlConnection conn = new SqlConnection(connString)) 
{
    conn.Open();
    using (var transaction = conn.BeginTransaction())
    {
        /* Update something in the database */
        /* Then call any registered OnUpdate handlers */
        InvokeOnUpdate(conn);

        transaction.Commit();
    }
}

InvokeonUpdate (IDBConnection Conn)에 대한 호출은 구현 및 등록 할 수있는 이벤트 핸들러를 호출합니다. 따라서이 핸들러에서는 IDBConnection 객체에 대한 참조가 있지만 보류중인 거래에 대한 참조는 없습니다. 거래를 보유 할 수있는 방법이 있습니까? onupdate 핸들러에서 다음과 비슷한 것을 실행하고 싶습니다.

private void MyOnUpdateHandler(IDbConnection conn) 
{
    var cmd = conn.CreateCommand();
    cmd.CommandText = someSQLString;
    cmd.CommandType = CommandType.Text;

    cmd.ExecuteNonQuery();
}

그러나 cmd.executenonquery ()에 대한 호출은 다음을 불평하는 InvalidoperationException을 던집니다.

"ExecuteNQuery는 명령에 할당 된 연결이 보류중인 지역 거래에있을 때 명령에 거래가 있어야합니다. 명령의 거래 속성이 초기화되지 않았습니다."

어떤 식 으로든 내 SQLCommand CMD를 보류중인 거래로 입대 할 수 있습니까? IDBConnection 객체에서 보류중인 거래에 대한 참조를 검색 할 수 있습니까 (필요한 경우 반사를 기꺼이 사용하게되어 기쁩니다).

도움이 되었습니까?

해결책

와우 나는 처음에 이것을 믿지 않았다. 나는 그것을 놀랐다 CreateCommand() 로컬 SQL Server Transactions를 사용할 때는 거래가 명령을 제공하지 않으며 트랜잭션이 SqlConnection 물체. 실제로 반영 할 때 SqlConnection 현재 트랜잭션은 해당 객체에 저장되지 않습니다. 편집 벨로우즈에서, 나는 당신에게 그들의 내부 클래스 중 일부를 통해 객체를 추적 할 힌트를 주었다.

메소드를 수정할 수는 없지만 메소드 바 주위에 트랜잭션 스코프를 사용할 수 있습니까? 그래서 당신이 가지고 있다면 :

public static void CallingFooBar()
{
   using (var ts=new TransactionScope())
   {
      var foo=new Foo();
      foo.Bar();
      ts.Complete();
   }
}

이것은 작동합니다. 귀하와 비슷한 코드를 사용하여 테스트했으며 일단 래퍼를 추가하면 물론이 작업을 수행 할 수있는 경우 모두 잘 작동합니다. 지적한 바와 같이, 더 이상의 연결이 내에서 하나의 연결이 열리면 TransactionScope 시스템이 구성되지 않으면 오류가 발생합니다.

DTC에 입대하는 것도 현지 거래보다 몇 배나 느려집니다.

편집하다

실제로 반사를 시도하고 사용하려면 SQLConnection에는 sqlinternalconnection이 있습니다. 이것은 sqlinternaltransaction을 반환하는 가용 인치 altransaction의 특성을 가지고 있습니다. 이것은 필요한 sqltransaction을 반환하는 부모의 특성을 가지고 있습니다.

다른 팁

누구나이를 달성하기 위해 반사 코드에 관심이있는 경우 다음과 같습니다.

    private static readonly PropertyInfo ConnectionInfo = typeof(SqlConnection).GetProperty("InnerConnection", BindingFlags.NonPublic | BindingFlags.Instance);
    private static SqlTransaction GetTransaction(IDbConnection conn) {
        var internalConn = ConnectionInfo.GetValue(conn, null);
        var currentTransactionProperty = internalConn.GetType().GetProperty("CurrentTransaction", BindingFlags.NonPublic | BindingFlags.Instance);
        var currentTransaction = currentTransactionProperty.GetValue(internalConn, null);
        var realTransactionProperty = currentTransaction.GetType().GetProperty("Parent", BindingFlags.NonPublic | BindingFlags.Instance);
        var realTransaction = realTransactionProperty.GetValue(currentTransaction, null);
        return (SqlTransaction) realTransaction;
    }

메모:

  • 유형은 내부 및 속성이 비공개이므로 동적을 사용할 수 없습니다.
  • 내부 유형은 또한 첫 번째 ConnectionInfo와 마찬가지로 중간 유형을 선언하지 않습니다. 물체에서 gettype를 사용해야합니다

Denis가 vb.net에서 만든 데니스 클래스의 C# 버전에 관심이있는 사람은 다음과 같습니다.

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;

namespace DataAccessLayer
{
    /// <summary>
    /// Decorator for the connection class, exposing additional info like it's transaction.
    /// </summary>
    public class ConnectionWithExtraInfo : IDbConnection
    {
        private IDbConnection connection = null;
        private IDbTransaction transaction = null;

        public IDbConnection Connection
        {
            get { return connection; }
        }

        public IDbTransaction Transaction
        {
            get { return transaction; }
        }

        public ConnectionWithExtraInfo(IDbConnection connection)
        {
            this.connection = connection;
        }

        #region IDbConnection Members

        public IDbTransaction BeginTransaction(IsolationLevel il)
        {
            transaction = connection.BeginTransaction(il);
            return transaction;
        }

        public IDbTransaction BeginTransaction()
        {
            transaction = connection.BeginTransaction();
            return transaction;
        }

        public void ChangeDatabase(string databaseName)
        {
            connection.ChangeDatabase(databaseName);
        }

        public void Close()
        {
            connection.Close();
        }

        public string ConnectionString
        {
            get 
            {
                return connection.ConnectionString; 
            }
            set 
            {
                connection.ConnectionString = value;
            }
        }

        public int ConnectionTimeout
        {
            get { return connection.ConnectionTimeout; }
        }

        public IDbCommand CreateCommand()
        {
            return connection.CreateCommand();
        }

        public string Database
        {
            get { return connection.Database; }
        }

        public void Open()
        {
            connection.Open();
        }

        public ConnectionState State
        {
            get { return connection.State; }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            connection.Dispose();
        }

        #endregion
    }
}

명령 개체는 생성자 중 하나를 사용하여 트랜잭션 객체 만 할당 할 수 있습니다. .NET 2.0 접근 방식으로 이동하여 System.Transactions 네임 스페이스 (전용 어셈블리가 있음).

   using System.Transactions;

    class Foo
    {   
        void Bar()
        {
            using (TransactionScope scope = new TransactionScope())
            {
                // Data access
                // ...
                scope.Complete()
            }
        }
    }

System.Transactions 접근 방식은 SQL Server 2005 LTM (Lightweight Transaction Coordinator)과 함께 사용합니다. 트랜잭션 범위에서 여러 연결 객체를 사용하지 않도록주의하십시오. 그렇지 않으면 트랜잭션이 분산 된 것으로 보이면 트랜잭션이 홍보됩니다. 이 더 리소스 집약적 인 트랜잭션 버전은 DTC에 의해 처리됩니다.

나는 단순한 제안자이므로 트랜잭션을 노출시키는 IDBConnection (Delegate Pattern)을 통해 래퍼를 작성하는 것은 어떻습니까? (VB.NET 코드에 대해 죄송합니다. 지금 VB.NET에 이것을 쓰고 있습니다) : 다음과 같은 것 :

  Public class MyConnection
      Implements IDbConnection

      Private itsConnection as IDbConnection
      Private itsTransaction as IDbTransaction

      Public Sub New(ByVal conn as IDbConnection)
         itsConnection = conn
      End Sub

      //...  'All the implementations would look like
      Public Sub Dispose() Implements IDbConnection.Dispose
         itsConnection.Dispose()
      End Sub
      //...

      //     'Except BeginTransaction which would look like
       Public Overridable Function BeginTransaction() As IDbTransaction Implements IDbConnection.BeginTransaction
         itsTransaction = itsConnection.BeginTransaction()
         Return itsTransaction
       End Function  


      // 'Now you can create a property and use it everywhere without any hacks
       Public ReadOnly Property Transaction
          Get
              return itsTransaction
          End Get
       End Property

    End Class

그래서 당신은 이것을 다음과 같이 인스턴스화 할 것입니다.

Dim myConn as new MyConnection(new SqlConnection(...))

그런 다음 다음을 사용하여 언제든지 거래를 얻을 수 있습니다.

 myConn.Transaction

.NET 4.5 에서이 문제에 직면 한 사람이있는 경우 사용할 수 있습니다. Transaction.Current안에 System.Transactions.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top