Führen Sie Lese- und Schreibvorgänge in derselben Transaktion durch

8

Ich versuche, Transaktionen in meiner Anwendung zu implementieren. Ich versuche nur, es wie im Beispiel in der BeginTransaction () -Dokumentation zu implementieren .

Public Shared Sub Process(wwid As String, trade_id As Integer, disposition As Boolean)

    Dim q As String
    Dim cmd, cmd_select As SqlCommand
    Dim reader As SqlDataReader
    Dim trans As SqlTransaction

    Dim user_id As Integer = User.CheckAuthentication(wwid)
    If user_id > 0 Then
        Using conn As New SqlConnection(CNGDB)
            conn.Open()
            '1. ReadUncommitted
            '2. ReadCommitted
            '3. RepeatableRead
            '4. Serializable
            '5. Snapshot
            trans = conn.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted)
            Try
                q = "UPDATE Trades SET Disposition = @disposition, FinalizedAt = @finalized_at" & _
                    " WHERE TradeID = @trade_id"
                cmd = New SqlCommand(q, conn)
                cmd.Transaction = trans
                cmd.Parameters.AddWithValue("@disposition", disposition)
                cmd.Parameters.AddWithValue("@finalized_at", DateTime.Now)
                cmd.Parameters.AddWithValue("@trade_id", trade_id)
                cmd.ExecuteNonQuery()
                If disposition = True Then
                    q = "SELECT Ownership_OwnershipID, Recipient_UserID FROM Trades" & _
                        " WHERE TradeID = @trade_id"
                    cmd_select = New SqlCommand(q, conn)
                    cmd_select.Transaction = trans
                    cmd_select.Parameters.AddWithValue("@trade_id", trade_id)
                    reader = cmd_select.ExecuteReader
                    reader.Read()
                    q = "UPDATE Ownerships SET User_UserID = @recipient_id" & _
                        " WHERE OwnershipID = @ownership_id"
                    cmd = New SqlCommand(q, conn)
                    cmd.Transaction = trans
                    cmd.Parameters.AddWithValue("@recipient_id", reader("Recipient_UserID"))
                    cmd.Parameters.AddWithValue("@ownership_id", reader("Ownership_OwnershipID"))
                    cmd.ExecuteNonQuery()
                End If
                trans.Commit()
            Catch ex As Exception
                Console.WriteLine("Commit Exception Type: {0}", ex.GetType())
                Console.WriteLine("  Message: {0}", ex.Message)
                Try
                    trans.Rollback()
                Catch ex2 As Exception
                    Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType())
                    Console.WriteLine("  Message: {0}", ex2.Message)
                End Try
            End Try

        End Using
    End If

End Sub

Das Problem ist, dass ich immer dann, wenn es auf Commit () trifft, den Fehler bekomme:

Commit-Ausnahmetyp: System.Data.SqlClient.SqlException

Nachricht: Die Transaktionsoperation kann nicht ausgeführt werden, da an dieser Transaktion ausstehende Anforderungen arbeiten.

Ich gehe davon aus, dass das Problem in der Tatsache liegt, dass ich versuche, aus dem zu lesen SELECT, um die Werte in der Sekunde zu füllen UPDATE, aber ich weiß nicht, wie es sonst gemacht werden kann. Ich habe versucht, die Datenbank IsolationLevel auf einige andere Werte zu setzen, und es ändert sich nichts.

Ich habe auch die TransactionScope- Klasse gefunden und ihr Beispiel für diesen Code implementiert. Als ich jedoch versuchte, diese Technik auf eine kompliziertere Gruppe von Vorgängen anzuwenden, wurde beanstandet, dass sie nicht mit dem Distributed Transaction Server meines lokalen Computers kommunizieren konnte. Ich schaltete es ein und das DTS stornierte diese andere, kompliziertere Transaktion aus einem nicht näher bezeichneten Grund, und ich wollte nicht in dieses Kaninchenloch gehen.

Kann jemand darauf hinweisen, was ich in meinem Code falsch mache?

Was ist der richtige Weg, um eine Reihe von SQL-Operationen in ein .NET-Programm zu verpacken? Vielleicht ist DTS der bessere Weg, aber diese Anwendung wird irgendwann in einer Azure-Datenbank gespeichert, und Azure scheint DTS ohnehin nicht zu unterstützen, obwohl es einige Ausdrücke über die "automatische Eskalation" von Isolationsstufen gibt, und ich bin verwirrt .

Ich habe in dieser Anwendung aus Gründen der Geschwindigkeit kurz mit gespeicherten Prozeduren gespielt, aber festgestellt, dass sie nicht schneller sind, und beschlossen, dass ich nicht versuchen möchte, Code in der Datenbank zu verwalten. Vielmehr wollte ich meine Datenbankzugriffsbibliothek in Visual Studio zusammen mit meiner GUI-Front-End-Anwendung (in VB) behalten, um (meistens) an einem Ort daran arbeiten zu können.

David Krider
quelle

Antworten:

8

Zunächst stimme ich Aaron Bertrand voll und ganz zu, als er in den Kommentaren vorschlug, dass Sie die SQL in eine gespeicherte Prozedur verschieben. Dies ist übrigens auch der klare Gewinner in Bezug auf "den richtigen Weg, eine Reihe von SQL-Operationen in ein .NET-Programm zu verpacken".

Da Sie sich Sorgen um die Verwaltung von Code an zwei Stellen machen, sollten Sie Datenbankprojekte in Visual Studio auschecken. Ich benutze diese jetzt schon eine Weile und es funktioniert wirklich gut. Wenn Sie diesen Weg gehen, erhalten Sie sogar einige praktische, wenn auch eingeschränkte Refactoring-Tools.

Ich sehe mehrere Probleme mit Ihrem Visual Basic-Code:

  • Sie entsorgen Ihr SqlCommand-Objekt nicht ordnungsgemäß.
  • Sie entsorgen Ihr SqlDataReader-Objekt nicht ordnungsgemäß.
  • Sie tun eigentlich nichts mit den vom Reader zurückgegebenen Daten, daher ist dies überflüssig.
  • Sie verwenden die Variable wieder, cmdwenn Sie keine disposition = Trueandere Variable verwenden. Ich bin nicht sicher, aber ich glaube, dies wird verhindern, dass die Transaktion das erste Update zurücksetzt. Unabhängig davon ist dies meiner Meinung nach keine bewährte Methode.
  • Sie sollten sich wahrscheinlich von der .AddWithValue()Methode entfernen . Wie in diesem Blogbeitrag beschrieben , können aufgrund impliziter Conversions Probleme auftreten.

Der Grund für das Schließen der SqlDataReaderArbeit, wie von Herrn Magoo in den Kommentaren vorgeschlagen und in Ihrem Kommentar bestätigt, war auch, dass der Leser verhindert, dass etwas anderes in der Verbindung passiert, bis sie geschlossen wird. Hier ist ein Zitat aus der Dokumentation :

Während der Verwendung des SqlDataReader ist die zugehörige SqlConnection damit beschäftigt, den SqlDataReader zu bedienen, und es können keine anderen Operationen an der SqlConnection ausgeführt werden, als sie zu schließen. Dies ist so lange der Fall, bis die Close-Methode des SqlDataReader aufgerufen wird. Beispielsweise können Sie Ausgabeparameter erst abrufen, nachdem Sie Close aufgerufen haben.

Weitere / verwandte Spielereien, die durch die Art und Weise verursacht werden können, wie Sie den Reader verwenden / missbrauchen, finden Sie in dieser Antwort unter Stapelüberlauf.


Noch ein paar Gedanken basierend auf Ihrem Kommentar:

Eine einfache Möglichkeit, ein Datenbankprojekt zu füllen, das erstellt wird, nachdem bereits eine Datenbank vorhanden ist, ist die Verwendung der Funktion "Schema Compare". In meiner Version von Visual Studio befindet sich diese Funktionalität in der Menüleiste unter: Extras -> SQL Server -> Vergleich neuer Schemata. Möglicherweise müssen Sie die neueste Version von SSDT (kostenlos) für Ihre Version von Visual Studio herunterladen , um diese Funktionalität zu erhalten.

Ich bin damit einverstanden, dass Sie sich in den nächsten Wochen wahrscheinlich nicht nur der Verlagerung Ihres Inline-SQL in gespeicherte Prozeduren widmen sollten. Wie ich in der Vergangenheit erwähnt habe, ist das Blockieren von Updates beim Refactor von Kernelementen Ihrer Anwendung selten der beste Plan. Sie können den gesamten neuen Datenzugriff in gespeicherte Prozeduren verschieben und SQL in gespeicherte Prozeduren verschieben, wenn Sie das Inline-SQL berühren müssen. Dies ist minimal invasiv, verteilt die Arbeit im Laufe der Zeit und ermöglicht es Ihnen, Ihre Codebasis (VB.NET und SQL) systematisch zu verbessern .

Erik
quelle
Du hast natürlich recht. Gemäß den Kommentaren "unter dem Falz" zur ursprünglichen Frage war es tatsächlich der SQLDataReader, der die Transaktion vom Abschluss abhielt. Dies war mein erstes Mal in .NET / SQL. Seitdem habe ich alle meine Klassen durchlaufen und alle meine Leser geschlossen und meine Befehle angeordnet. Interessanter Kommentar zur .AddWithValue () -Methode. Außerdem hatte ich keine Ahnung von Datenbankprojekten in VS. Ich muss das untersuchen, obwohl es wahrscheinlich viel zu spät ist, alles als gespeicherte Prozedur umzugestalten.
David Krider