Frage

Angenommen, jemand (außer mir) schreibt den folgenden Code und kompiliert ihn in eine Versammlung:

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();
    }
}

Der Anruf bei InvokeonUpdate (IDBConnection Conn) ruft an einen Ereignishandler, den ich implementieren und registrieren kann. In diesem Handler werde ich also einen Hinweis auf das IdbConnection -Objekt haben, aber ich werde keinen Bezug auf die anhängige Transaktion haben. Gibt es eine Möglichkeit, wie ich die Transaktion erhalten kann? In meinem Onupdate -Handler möchte ich etwas Ähnliches wie folgt ausführen:

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

    cmd.ExecuteNonQuery();
}

Der Aufruf an cmd.executenonQuery () wirft jedoch eine InvalidoperationException aus, die sich darüber beschwert

"ExecutenOnQuery erfordert, dass der Befehl eine Transaktion abhält, wenn die dem Befehl zugewiesene Verbindung in einer ausstehenden lokalen Transaktion liegt. Die Transaktionseigenschaft des Befehls wurde nicht initialisiert".

Kann ich meine SQLCommand -CMD in irgendeiner Weise mit der ausstehenden Transaktion einbeziehen? Kann ich einen Verweis auf die anhängige Transaktion aus dem IdbConnection -Objekt abrufen (ich würde gerne bei Bedarf Reflexion verwenden)?

War es hilfreich?

Lösung

Wow, ich habe das zuerst nicht geglaubt. Ich bin überrascht, dass ich das überrascht habe CreateCommand() Gibt dem Befehl keine Transaktion bei der Verwendung lokaler SQL -Servertransaktionen und der Transaktion ist nicht auf der SqlConnection Objekt. Eigentlich beim Nachdenken über SqlConnection Die aktuelle Transaktion wird in diesem Objekt nicht einmal gespeichert. Im Bearbeitungsbrüll habe ich Ihnen einige Hinweise gegeben, um das Objekt über einige ihrer internen Klassen aufzuspüren.

Ich weiß, dass Sie die Methode nicht ändern können, aber könnten Sie ein Transactionscope um die Methodenleiste verwenden? Also, wenn du hast:

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

Dies wird funktionieren, ich habe mit ähnlichem Code mit Ihrem getestet und sobald ich den Wrapper hinzufüge, funktioniert alles einwandfrei, wenn Sie dies natürlich tun können. Wie ausgedrückt, achten Sie auf, wenn mehr eine Verbindung innerhalb der geöffnet ist TransactionScope Sie werden zu einer verteilten Transaktion eskaliert. Wenn Ihr System nicht dafür konfiguriert ist, erhalten Sie einen Fehler.

Die Einstellung mit dem DTC ist auch mehrmals langsamer als eine lokale Transaktion.

Bearbeiten

Wenn Sie wirklich versuchen möchten, Reflection zu verwenden, verfügt SQLConnection über eine SQlinternalConnection. Dies hat wiederum eine Eigenschaft von verfügbarenInternaltransaction, die eine SQLINTENTELTRANTRANSACTIONACE zurückgibt. Dies hat eine Eigenschaft von übergeordnetem, die die benötigten SQLTransaction zurückgibt.

Andere Tipps

Falls jemand an dem Reflexionscode interessiert ist, um dies zu erreichen, heißt es hier:

    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;
    }

Anmerkungen:

  • Die Typen sind intern und die Eigenschaften privat, sodass Sie keine Dynamik verwenden können
  • Interne Typen verhindern auch, dass Sie die Zwischentypen deklarieren, wie ich es beim ersten ConnectionInfo getan habe. Ich muss Gettype für die Objekte verwenden

Für jeden, der sich für die C# -Version der Dekorateurin interessiert, die Denis in VB.NET gemacht hat, ist es hier:

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
    }
}

Dem Befehlsobjekt kann nur ein Transaktionsobjekt mit einem seiner Konstruktoren zugewiesen werden. Sie können sich für den .NET 2.0 -Ansatz entscheiden und ein Transactionscope -Objekt verwenden, das in der definiert ist System.Transactions Namespace (hat eine spezielle Baugruppe).

   using System.Transactions;

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

Der Ansatz des Systems.Transactions verwendet in Verbindung mit SQL Server 2005, einem leichten Transaktionskoordinator (LTM). Achten Sie darauf, dass Sie nicht mehrere Verbindungsobjekte in Ihrem Transaktionsbereich verwenden, da die Transaktion als verteilt angesehen wird. Diese ressourcenintensivere Version der Transaktion wird dann von DTC bearbeitet.

Ich bin ein großer Befürworter von einfachem. (Entschuldigung für VB.NET -Code, ich schreibe dies gerade in VB.NET) Etwas wie folgt:

  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

Sie würden dies also als:

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

Und dann können Sie die Transaktion jederzeit mit verwenden:

 myConn.Transaction

Falls jeder mit diesem Problem auf .NET 4.5 konfrontiert war, können Sie verwenden Transaction.Currentin System.Transactions.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top