Freigeben über


Umschließen von Datenbankänderungen innerhalb einer Transaktion (VB)

von Scott Mitchell

PDF herunterladen

Dieses Lernprogramm ist das erste von vier, das sich mit dem Aktualisieren, Löschen und Einfügen von Datenbatches befasst. In diesem Lernprogramm erfahren Sie, wie Datenbanktransaktionen die Durchführung von Batchänderungen als atomer Vorgang ermöglichen, wodurch sichergestellt wird, dass entweder alle Schritte erfolgreich sind oder alle Schritte fehlschlagen.

Einleitung

Beginnend mit dem Ein Überblick über das Einfügen, Aktualisieren und Löschen von Daten-Tutorial bietet GridView eingebaute Unterstützung für die Bearbeitung und das Löschen auf Zeilenebene. Mit wenigen Mausklicks ist es möglich, eine umfangreiche Datenbearbeitungsschnittstelle zu erstellen, ohne eine einzige Codezeile zu schreiben, solange Sie mit der Bearbeitung und dem Löschen auf Zeilenbasis zufrieden sind. In bestimmten Szenarien ist dies jedoch nicht ausreichend, und wir müssen Benutzern die Möglichkeit bieten, eine Reihe von Datensätzen zu bearbeiten oder zu löschen.

Beispielsweise verwenden die meisten webbasierten E-Mail-Clients ein Raster, um jede Nachricht auflisten zu können, in der jede Zeile ein Kontrollkästchen zusammen mit den E-Mail-Informationen (Betreff, Absender usw.) enthält. Diese Benutzeroberfläche ermöglicht es dem Benutzer, mehrere Nachrichten zu löschen, indem er sie überprüft und dann auf eine Schaltfläche "Ausgewählte Nachrichten löschen" klickt. Eine Batchbearbeitungsschnittstelle ist ideal in Situationen, in denen Benutzer häufig viele verschiedene Datensätze bearbeiten. Anstatt den Benutzer zu zwingen, auf "Bearbeiten" zu klicken, seine Änderung vorzunehmen und dann für jeden Datensatz, der geändert werden muss, auf "Aktualisieren" zu klicken, stellt eine Batchbearbeitungsschnittstelle jede Zeile mit der zugehörigen Bearbeitungsschnittstelle dar. Der Benutzer kann schnell den Satz von Zeilen ändern, die geändert werden müssen, und diese Änderungen dann speichern, indem er auf eine Schaltfläche "Alle aktualisieren" klickt. In dieser Reihe von Lernprogrammen untersuchen wir, wie Schnittstellen zum Einfügen, Bearbeiten und Löschen von Datenbatches erstellt werden.

Beim Ausführen von Batchvorgängen ist es wichtig zu bestimmen, ob es möglich sein sollte, dass einige der Vorgänge im Batch erfolgreich ausgeführt werden können, während andere fehlschlagen. Erwägen Sie eine Batchlöschschnittstelle – was sollte passieren, wenn der erste ausgewählte Datensatz erfolgreich gelöscht wird, der zweite jedoch aufgrund einer Verletzung der Fremdschlüsseleinschränkung fehlschlägt? Sollte das Löschen des ersten Datensatzes rückgängig gemacht werden oder ist es akzeptabel, dass der erste Datensatz gelöscht wird?

Wenn der Batchvorgang als atomischer Vorgang behandelt werden soll, einer, bei dem entweder alle Schritte erfolgreich sind oder alle Schritte fehlschlagen, muss die Datenzugriffsschicht erweitert werden, um die Unterstützung für Datenbanktransaktionen einzuschließen. Datenbanktransaktionen garantieren die Atomität für die Gruppe von INSERT, UPDATEund DELETE Anweisungen, die unter dem Dach der Transaktion ausgeführt werden und sind ein Feature, das von den meisten modernen Datenbanksystemen unterstützt wird.

In diesem Lernprogramm erfahren Sie, wie Sie die DAL erweitern, um Datenbanktransaktionen zu verwenden. Nachfolgende Lernprogramme untersuchen die Implementierung von Webseiten zum Einfügen, Aktualisieren und Löschen von Schnittstellen im Batch. Los geht's!

Hinweis

Beim Ändern von Daten in einer Batchtransaktion ist die Atomität nicht immer erforderlich. In einigen Szenarien kann es akzeptabel sein, dass einige Datenänderungen erfolgreich sind und andere im selben Batch fehlschlagen, z. B. beim Löschen einer Reihe von E-Mails aus einem webbasierten E-Mail-Client. Wenn während des Löschvorgangs ein Datenbankfehler auftritt, ist es wahrscheinlich akzeptabel, dass diese Datensätze, die ohne Fehler verarbeitet wurden, gelöscht werden. In solchen Fällen muss die DAL nicht geändert werden, um Datenbanktransaktionen zu unterstützen. Es gibt jedoch noch andere Batchbetriebsszenarien, in denen Atomität von entscheidender Bedeutung ist. Wenn ein Kunde ihr Guthaben von einem Bankkonto auf ein anderes verschiebt, müssen zwei Operationen durchgeführt werden: Die Mittel müssen vom ersten Konto abgezogen und dann zum zweiten hinzugefügt werden. Zwar mag es der Bank egal sein, wenn der erste Schritt gelingt, aber der zweite Schritt fehlschlägt, doch ihre Kunden wären verständlicherweise verärgert. Ich ermutige Sie, dieses Lernprogramm durchzuarbeiten und die Verbesserungen des DAL zu implementieren, um Datenbanktransaktionen zu unterstützen, auch wenn Sie sie nicht in der Batcheinfügung, Aktualisierung und Löschung von Schnittstellen verwenden möchten, die in den folgenden drei Lernprogrammen erstellt werden.

Übersicht über Transaktionen

Die meisten Datenbanken enthalten Unterstützung für Transaktionen, die es ermöglichen, dass mehrere Datenbankbefehle in einer einzigen logischen Arbeitseinheit gruppiert werden. Die Datenbankbefehle, die eine Transaktion umfassen, sind garantiert atomar, was bedeutet, dass entweder alle Befehle fehlschlagen oder alle erfolgreich sind.

Im Allgemeinen werden Transaktionen über SQL-Anweisungen mithilfe des folgenden Musters implementiert:

  1. Geben Sie den Beginn einer Transaktion an.
  2. Führen Sie die SQL-Anweisungen aus, die die Transaktion umfassen.
  3. Wenn in einer der Anweisungen aus Schritt 2 ein Fehler auftritt, führen Sie ein Rollback der Transaktion durch.
  4. Wenn alle Anweisungen aus Schritt 2 ohne Fehler abgeschlossen sind, schließen Sie die Transaktion ab.

Die zum Erstellen, Ausführen und Zurückrollen der Transaktion verwendeten SQL-Anweisungen können manuell eingegeben werden, wenn SQL-Skripts geschrieben oder gespeicherte Prozeduren erstellt werden, oder programmatisch mithilfe von ADO.NET oder den Klassen im System.Transactions Namespace. In diesem Lernprogramm untersuchen wir nur die Verwaltung von Transaktionen mit ADO.NET. In einem zukünftigen Tutorial befassen wir uns mit der Verwendung gespeicherter Prozeduren in der Datenzugriffsebene, wobei wir die SQL-Anweisungen zum Erstellen, Rückgängigmachen und Committen von Transaktionen untersuchen. Lesen Sie in der Zwischenzeit die Verwaltung von Transaktionen in gespeicherten SQL Server-Prozeduren , um weitere Informationen zu erhalten.

Hinweis

Die TransactionScope Klasse im System.Transactions Namespace ermöglicht Entwicklern das programmgesteuerte Umschließen einer Reihe von Anweisungen im Rahmen einer Transaktion und umfasst Unterstützung für komplexe Transaktionen, die mehrere Quellen umfassen, z. B. zwei verschiedene Datenbanken oder sogar heterogene Datentypen, z. B. eine Microsoft SQL Server-Datenbank, eine Oracle-Datenbank und einen Webdienst. Ich habe mich entschieden, ADO.NET Transaktionen für dieses Lernprogramm anstelle der TransactionScope Klasse zu verwenden, da ADO.NET für Datenbanktransaktionen spezifisch ist und in vielen Fällen viel weniger ressourcenintensiv ist. Darüber hinaus verwendet die TransactionScope Klasse unter bestimmten Szenarien den Microsoft Distributed Transaction Coordinator (MSDTC). Die Konfigurations-, Implementierungs- und Leistungsprobleme im Zusammenhang mit MSDTC machen es zu einem eher spezialisierten und fortgeschrittenen Thema und über den Umfang dieser Lernprogramme hinaus.

Beim Arbeiten mit dem SqlClient-Anbieter in ADO.NET werden Transaktionen über einen Aufruf der SqlConnection KlassenmethodeBeginTransactioninitiiert, die ein SqlTransaction Objekt zurückgibt. Die Datenänderungsanweisungen, die die Transaktion bilden, werden in einem try...catch Block platziert. Wenn in einer Anweisung im try Block ein Fehler auftritt, wird die Ausführung an den catch Block übertragen, in dem die Transaktion über die Methode des SqlTransaction Objekts Rollbackzurückgesetzt werden kann. Wenn alle Anweisungen erfolgreich abgeschlossen wurden, wird die Transaktion durch einen Aufruf der SqlTransaction Methode des Objekts Commit am Ende des try Blocks festgeschrieben. Der folgende Codeausschnitt veranschaulicht dieses Muster.

' Create the SqlTransaction object
Dim myTransaction As SqlTransaction = SqlConnectionObject.BeginTransaction();
Try
    '
    ' ... Perform the database transaction�s data modification statements...
    '
    ' If we reach here, no errors, so commit the transaction
    myTransaction.Commit()
Catch
    ' If we reach here, there was an error, so rollback the transaction
    myTransaction.Rollback()
    Throw
End Try

Standardmäßig verwenden die TableAdapters in einem typierten DataSet keine Transaktionen. Um Transaktionen zu unterstützen, müssen wir die TableAdapter-Klassen erweitern, um zusätzliche Methoden einzuschließen, die das obige Muster verwenden, um eine Reihe von Datenänderungsanweisungen im Rahmen einer Transaktion auszuführen. In Schritt 2 wird gezeigt, wie Sie partielle Klassen verwenden, um diese Methoden hinzuzufügen.

Schritt 1: Erstellen der Webseiten zum Umgang mit gruppierten Daten

Bevor wir uns mit der Erweiterung des DAL zur Unterstützung von Datenbanktransaktionen befassen, nehmen wir uns zunächst einen Moment Zeit, um die ASP.NET Webseiten zu erstellen, die wir für dieses Lernprogramm und die drei folgenden Benötigen. Fügen Sie zunächst einen neuen Ordner mit dem Namen BatchData hinzu, und fügen Sie dann die folgenden ASP.NET Seiten hinzu, wobei jede Seite der Site.master Gestaltungsvorlage zugeordnet wird.

  • Default.aspx
  • Transactions.aspx
  • BatchUpdate.aspx
  • BatchDelete.aspx
  • BatchInsert.aspx

Hinzufügen der ASP.NET Seiten für die SqlDataSource-Related Lernprogramme

Abbildung 1: Hinzufügen der ASP.NET Seiten für die SqlDataSource-Related Lernprogramme

Wie bei den anderen Ordnern wird Default.aspx das SectionLevelTutorialListing.ascx Benutzersteuerelement verwenden, um die Lernprogramme in seinem Abschnitt aufzulisten. Fügen Sie dieses Benutzersteuerelement zu Default.aspx hinzu, indem Sie es aus dem Projektmappen-Explorer auf die Entwurfsansicht der Seite ziehen.

Hinzufügen des SectionLevelTutorialListing.ascx-Benutzersteuerelements zu Default.aspx

Abbildung 2: Hinzufügen des SectionLevelTutorialListing.ascx Benutzersteuerelements zu Default.aspx (Klicken, um das Bild in voller Größe anzuzeigen)

Fügen Sie diese vier Seiten schließlich als Einträge zur Web.sitemap Datei hinzu. Fügen Sie insbesondere das folgende Markup nach dem Anpassen der Sitemap <siteMapNode> hinzu:

<siteMapNode title="Working with Batched Data" 
    url="~/BatchData/Default.aspx" 
    description="Learn how to perform batch operations as opposed to 
                 per-row operations.">
    
    <siteMapNode title="Adding Support for Transactions" 
        url="~/BatchData/Transactions.aspx" 
        description="See how to extend the Data Access Layer to support 
                     database transactions." />
    <siteMapNode title="Batch Updating" 
        url="~/BatchData/BatchUpdate.aspx" 
        description="Build a batch updating interface, where each row in a 
                      GridView is editable." />
    <siteMapNode title="Batch Deleting" 
        url="~/BatchData/BatchDelete.aspx" 
        description="Explore how to create an interface for batch deleting 
                     by adding a CheckBox to each GridView row." />
    <siteMapNode title="Batch Inserting" 
        url="~/BatchData/BatchInsert.aspx" 
        description="Examine the steps needed to create a batch inserting 
                     interface, where multiple records can be created at the 
                     click of a button." />
</siteMapNode>

Nehmen Sie sich nach dem Aktualisieren Web.sitemap einen Moment Zeit, um die Tutorials-Website in einem Browser zu betrachten. Das Menü auf der linken Seite enthält jetzt Elemente für die Arbeit mit Batch-Daten-Tutorials.

Die Sitemap enthält jetzt Einträge für die Batchdaten-Tutorials

Abbildung 3: Die Sitemap enthält jetzt Einträge für Tutorials zur Arbeit mit Batchdaten.

Schritt 2: Aktualisieren der Datenzugriffsebene zur Unterstützung von Datenbanktransaktionen

Wie wir bereits im ersten Tutorial besprochen haben, besteht das typisierte DataSet in unserem DAL aus DataTables und TableAdapters. Die DataTables enthalten Daten, während die TableAdapters die Funktionalität zum Lesen von Daten aus der Datenbank in die DataTables bereitstellen, um die Datenbank mit Änderungen zu aktualisieren, die an den DataTables vorgenommen wurden, usw. Denken Sie daran, dass die TableAdapters zwei Muster zum Aktualisieren von Daten bereitstellen, die ich als Batchaktualisierung und DB-Direct bezeichnet habe. Mit dem Batchaktualisierungsmuster übergibt man den TableAdapter an ein DataSet- oder DataTable-Objekt oder eine Sammlung von DataRows. Diese Daten werden aufgezählt und für jede eingefügte, geänderte oder gelöschte Zeile, das InsertCommand, UpdateCommandoder DeleteCommand wird ausgeführt. Mit dem DB-Direct Muster wird der TableAdapter stattdessen die Werte der Spalten übergeben, die zum Einfügen, Aktualisieren oder Löschen eines einzelnen Datensatzes erforderlich sind. Die DB Direct-Mustermethode verwendet dann die übergebenen Werte, um die entsprechende InsertCommand, UpdateCommandoder DeleteCommand Anweisung auszuführen.

Unabhängig vom verwendeten Updatemuster verwenden die automatisch generierten TableAdapters-Methoden keine Transaktionen. Standardmäßig wird jeder vom TableAdapter ausgeführte Einfüge-, Aktualisierungs- oder Löschvorgang als einzelner einzelner Vorgang behandelt. Stellen Sie sich beispielsweise vor, dass das DB-Direct Muster von einem Code in der BLL verwendet wird, um zehn Datensätze in die Datenbank einzufügen. Dieser Code ruft die TableAdapter-Methode Insert zehnmal auf. Wenn die ersten fünf Einfügungen erfolgreich sind, der sechste jedoch zu einer Ausnahme führte, würden die ersten fünf eingefügten Datensätze in der Datenbank verbleiben. Wenn das Batchaktualisierungsmuster verwendet wird, um Einfügungen, Aktualisierungen und Löschvorgänge an den eingefügten, geänderten und gelöschten Zeilen in einer DataTable durchzuführen, bleiben die vorherigen Änderungen in der Datenbank, wenn die ersten Änderungen erfolgreich waren, aber eine spätere einen Fehler verursachte.

In bestimmten Szenarien möchten wir die Atomität in einer Reihe von Modifikationen sicherstellen. Dazu müssen wir den TableAdapter manuell erweitern, indem neue Methoden hinzugefügt werden, die die InsertCommand, UpdateCommand, und DeleteCommand unter dem Dach einer Transaktion ausgeführt werden sollen. Beim Erstellen einer Datenzugriffsebene haben wir uns mit partiellen Klassen befasst, um die Funktionalität der DataTables innerhalb des typierten DataSets zu erweitern. Diese Technik kann auch mit TableAdapters verwendet werden.

Das typierte DataSet Northwind.xsd befindet sich im App_Code Unterordner des Ordners DAL . Erstellen Sie einen Unterordner im DAL Ordner namens TransactionSupport , und fügen Sie eine neue Klassendatei mit dem Namen hinzu ProductsTableAdapter.TransactionSupport.vb (siehe Abbildung 4). Diese Datei wird die partielle Implementierung von ProductsTableAdapter enthalten, die Methoden zur Durchführung von Datenänderungen mithilfe einer Transaktion einschließt.

Hinzufügen eines Ordners mit dem Namen

Abbildung 4: Hinzufügen eines Ordners namens TransactionSupport und einer Klassendatei mit dem Namen ProductsTableAdapter.TransactionSupport.vb

Geben Sie den folgenden Code in die ProductsTableAdapter.TransactionSupport.vb Datei ein:

Imports System.Data
Imports System.Data.SqlClient
Namespace NorthwindTableAdapters
    Partial Public Class ProductsTableAdapter
        Private _transaction As SqlTransaction
        Private Property Transaction() As SqlTransaction
            Get
                Return Me._transaction
            End Get
            Set(ByVal Value As SqlTransaction)
                Me._transaction = Value
            End Set
        End Property
        Public Sub BeginTransaction()
            ' Open the connection, if needed
            If Me.Connection.State <> ConnectionState.Open Then
                Me.Connection.Open()
            End If
            ' Create the transaction and assign it to the Transaction property
            Me.Transaction = Me.Connection.BeginTransaction()
            ' Attach the transaction to the Adapters
            For Each command As SqlCommand In Me.CommandCollection
                command.Transaction = Me.Transaction
            Next
            Me.Adapter.InsertCommand.Transaction = Me.Transaction
            Me.Adapter.UpdateCommand.Transaction = Me.Transaction
            Me.Adapter.DeleteCommand.Transaction = Me.Transaction
        End Sub
        Public Sub CommitTransaction()
            ' Commit the transaction
            Me.Transaction.Commit()
            ' Close the connection
            Me.Connection.Close()
        End Sub
        Public Sub RollbackTransaction()
            ' Rollback the transaction
            Me.Transaction.Rollback()
            ' Close the connection
            Me.Connection.Close()
        End Sub
    End Class
End Namespace

Das Schlüsselwort Partial in der Klassendeklaration hier weist den Compiler darauf hin, dass die hinzugefügten Member zur Klasse ProductsTableAdapter im Namespace NorthwindTableAdapters hinzugefügt werden sollen. Notieren Sie sich die Imports System.Data.SqlClient Anweisung am Anfang der Datei. Da der TableAdapter für die Verwendung des SqlClient-Anbieters konfiguriert wurde, verwendet es intern ein SqlDataAdapter Objekt, um seine Befehle für die Datenbank ausstellen zu können. Daher müssen wir die SqlTransaction Klasse verwenden, um die Transaktion zu starten und sie dann zu committen oder zurückzurollen. Wenn Sie einen anderen Datenspeicher als Microsoft SQL Server verwenden, müssen Sie den entsprechenden Anbieter verwenden.

Diese Methoden stellen die Bausteine bereit, die zum Starten, Rollback und Commit einer Transaktion erforderlich sind. Sie sind gekennzeichnet Public, sodass sie aus der ProductsTableAdapter, aus einer anderen Klasse im DAL oder aus einer anderen Ebene in der Architektur, wie die BLL, verwendet werden können. BeginTransaction öffnet das interne SqlConnection TableAdapter-Objekt (falls erforderlich), beginnt die Transaktion und weist sie der Transaction Eigenschaft zu und fügt die Transaktion an die internen SqlDataAdapter Objekte an SqlCommand . CommitTransaction und RollbackTransaction rufen die Methoden Transaction und Commit des Rollback Objektes auf, bevor sie das interne Connection Objekt schließen.

Schritt 3: Hinzufügen von Methoden zum Aktualisieren und Löschen von Daten unter dem Dach einer Transaktion

Mit diesen Methoden sind wir bereit, entweder ProductsDataTable oder der BLL Methoden hinzuzufügen, die eine Reihe von Befehlen unter dem Dach einer Transaktion ausführen. Die folgende Methode verwendet das Batchaktualisierungsmuster, um eine Instanz mithilfe einer ProductsDataTable Transaktion zu aktualisieren. Sie startet eine Transaktion durch Aufrufen der BeginTransaction Methode und verwendet dann einen Try...Catch Block, um die Datenänderungsanweisungen ausstellen zu können. Wenn der Aufruf der Methode des Adapter Objekts Update zu einer Ausnahme führt, wird die Ausführung an den catch Block übertragen, in dem die Transaktion zurückgesetzt wird und die Ausnahme erneut ausgelöst wird. Denken Sie daran, dass die Update-Methode das Batchaktualisierungsmuster implementiert, indem sie die Zeilen des bereitgestellten ProductsDataTable auflistet und die erforderlichen InsertCommand, UpdateCommand und DeleteCommand-Operationen durchführt. Wenn einer dieser Befehle zu einem Fehler führt, wird die Transaktion zurückgesetzt und die vorherigen Änderungen während der Lebensdauer der Transaktion rückgängig gemacht. Sollte die Update Anweisung ohne Fehler abgeschlossen sein, wird die Transaktion vollständig durchgeführt.

Public Function UpdateWithTransaction _
    (ByVal dataTable As Northwind.ProductsDataTable) As Integer
    
    Me.BeginTransaction()
    Try
        ' Perform the update on the DataTable
        Dim returnValue As Integer = Me.Adapter.Update(dataTable)
        ' If we reach here, no errors, so commit the transaction
        Me.CommitTransaction()
        Return returnValue
    Catch
        ' If we reach here, there was an error, so rollback the transaction
        Me.RollbackTransaction()
        Throw
    End Try
End Function

Fügen Sie die UpdateWithTransaction Methode der ProductsTableAdapter Klasse durch die partielle Klasse in ProductsTableAdapter.TransactionSupport.vb hinzu. Alternativ kann diese Methode der Business Logic Layer-Klasse ProductsBLL mit einigen geringfügigen syntaktischen Änderungen hinzugefügt werden. Das Schlüsselwort Me in Me.BeginTransaction(), Me.CommitTransaction() und Me.RollbackTransaction() muss durch Adapter ersetzt werden (denken Sie daran, dass Adapter der Name einer Eigenschaft in ProductsBLL vom Typ ProductsTableAdapter ist).

Die UpdateWithTransaction Methode verwendet das Batchaktualisierungsmuster, aber eine Reihe von DB-Direct Aufrufen kann auch innerhalb des Bereichs einer Transaktion verwendet werden, wie die folgende Methode zeigt. Die DeleteProductsWithTransaction Methode akzeptiert als Eingabe ein List(Of T) vom Typ Integer, das die zu löschenden ProductID umfasst. Die Methode initiiert die Transaktion über einen Aufruf von BeginTransaction und durchläuft dann im Try-Block die angegebene Liste, wobei sie für jeden Delete Wert die DB-Direct-Mustermethode ProductID aufruft. Wenn einer der Aufrufe Delete fehlschlägt, wird die Kontrolle dem Catch-Block übergeben, in dem die Transaktion zurückgesetzt wird und die Ausnahme erneut geworfen wird. Wenn alle Aufrufe Delete erfolgreich ausgeführt werden, wird die Transaktion bestätigt. Fügen Sie diese Methode der ProductsBLL Klasse hinzu.

Public Sub DeleteProductsWithTransaction _
    (ByVal productIDs As System.Collections.Generic.List(Of Integer))
    
    ' Start the transaction
    Adapter.BeginTransaction()
    Try
        ' Delete each product specified in the list
        For Each productID As Integer In productIDs
            Adapter.Delete(productID)
        Next
        ' Commit the transaction
        Adapter.CommitTransaction()
    Catch
        ' There was an error - rollback the transaction
        Adapter.RollbackTransaction()
        Throw
    End Try
End Sub

Anwenden von Transaktionen auf mehrere TableAdapters

Der in diesem Tutorial untersuchte transaktionsbezogene Code ermöglicht es, mehrere Anweisungen gegen ProductsTableAdapter als Atomoperation zu behandeln. Aber was geschieht, wenn mehrere Änderungen an verschiedenen Datenbanktabellen atomisch durchgeführt werden müssen? Wenn Sie beispielsweise eine Kategorie löschen, möchten wir ihre aktuellen Produkte möglicherweise zuerst einer anderen Kategorie zuweisen. Diese beiden Schritte, um die Produkte neu zuzuweisen und die Kategorie zu löschen, sollten als Atomoperation ausgeführt werden. Dies ProductsTableAdapter umfasst jedoch nur Methoden zum Ändern der Products Tabelle und enthält CategoriesTableAdapter nur Methoden zum Ändern der Categories Tabelle. Wie kann eine Transaktion also sowohl TableAdapters umfassen?

Eine Option besteht darin, eine Methode namens CategoriesTableAdapter zu DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID) hinzuzufügen, die eine gespeicherte Prozedur aufruft. Diese Prozedur weist die Produkte neu zu und löscht die Kategorie innerhalb des Rahmens einer in der gespeicherten Prozedur definierten Transaktion. In einem zukünftigen Lernprogramm erfahren Sie, wie Sie Transaktionen mit gespeicherten Prozeduren beginnen, übernehmen und zurücksetzen.

Eine weitere Option besteht darin, eine Hilfsklasse in der DAL zu erstellen, die die DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID) Methode enthält. Diese Methode würde eine Instanz von CategoriesTableAdapter und eine von ProductsTableAdapter erstellen und dann die Eigenschaften der beiden TableAdapter Connection auf dieselbe SqlConnection-Instanz setzen. Zu diesem Zeitpunkt würde einer der beiden TableAdapters die Transaktion mit einem Aufruf an BeginTransaction initiieren. Die Methoden der TableAdapters zum erneuten Zuweisen der Produkte und Löschen der Kategorie würden in einem Try...Catch-Block aufgerufen, wobei die Transaktion je nach Bedarf zugesichert oder zurückgesetzt werden kann.

Schritt 4: Hinzufügen derUpdateWithTransactionMethode zur Geschäftslogikebene

In Schritt 3 haben wir eine UpdateWithTransaction Methode zur ProductsTableAdapter in der DAL hinzugefügt. Wir sollten der BLL eine entsprechende Methode hinzufügen. Während die Präsentationsschicht direkt die Methode UpdateWithTransaction im DAL aufrufen könnte, haben diese Tutorials versucht, eine mehrschichtige Architektur zu definieren, die den DAL von der Präsentationsschicht isoliert. Daher ist es sinnvoll, diesen Ansatz fortzusetzen.

Öffnen Sie die ProductsBLL Klassendatei und fügen Sie eine Methode UpdateWithTransaction hinzu, die einfach die entsprechende DAL-Methode aufruft. Aus diesem Grund sollten jetzt zwei neue Methoden in ProductsBLL vorhanden sein: UpdateWithTransaction, die Sie soeben hinzugefügt haben, und DeleteProductsWithTransaction, die in Schritt 3 hinzugefügt wurde.

Public Function UpdateWithTransaction _
    (ByVal products As Northwind.ProductsDataTable) As Integer
    
    Return Adapter.UpdateWithTransaction(products)
End Function
Public Sub DeleteProductsWithTransaction _
    (ByVal productIDs As System.Collections.Generic.List(Of Integer))
    
    ' Start the transaction
    Adapter.BeginTransaction()
    Try
        ' Delete each product specified in the list
        For Each productID As Integer In productIDs
            Adapter.Delete(productID)
        Next
        ' Commit the transaction
        Adapter.CommitTransaction()
    Catch
        ' There was an error - rollback the transaction
        Adapter.RollbackTransaction()
        Throw
    End Try
End Sub

Hinweis

Diese Methoden enthalten nicht das Attribut, das den meisten anderen Methoden in der DataObjectMethodAttribute-Klasse zugewiesen ist, da wir diese Methoden direkt aus den Code-Behind-Klassen der ASP.NET-Seiten aufrufen werden. Erinnern Sie sich daran, dass DataObjectMethodAttribute verwendet wird, um zu kennzeichnen, welche Methoden im Assistenten zum Konfigurieren von Datenquellen des ObjectDataSource und unter welcher Registerkarte (SELECT, UPDATE, INSERT oder DELETE) angezeigt werden sollen. Da die GridView keine integrierte Unterstützung für die Batchbearbeitung oder -löschung bietet, müssen wir diese Methoden programmgesteuert aufrufen, anstatt den codefreien deklarativen Ansatz zu verwenden.

Schritt 5: Atomare Aktualisierung von Datenbankdaten aus der Präsentationsebene

Um den Effekt zu veranschaulichen, den die Transaktion beim Aktualisieren einer Reihe von Datensätzen hat, erstellen wir eine Benutzeroberfläche, die alle Produkte in einer GridView auflistet und ein Webschaltflächen-Steuerelement enthält, das beim Klicken die Produktwerte CategoryID neu zuweist. Insbesondere wird die Kategorieneuzuweisung vorankommen, sodass den ersten mehreren Produkten ein gültiger CategoryID Wert zugewiesen wird, während anderen absichtlich ein nicht vorhandener CategoryID Wert zugewiesen wird. Wenn wir versuchen, die Datenbank mit einem Produkt zu aktualisieren, dessen CategoryID Übereinstimmung nicht mit einer vorhandenen Kategorie CategoryIDübereinstimmt, tritt eine Verletzung der Fremdschlüsseleinschränkung auf und eine Ausnahme wird ausgelöst. Was wir in diesem Beispiel sehen werden, ist, dass beim Verwenden einer Transaktion die Ausnahme, die von der Verletzung der Fremdschlüsseleinschränkung ausgelöst wurde, dazu führt, dass die vorherigen gültigen CategoryID Änderungen rückgängig gemacht werden. Wenn Sie jedoch keine Transaktion verwenden, bleiben die Änderungen an den anfänglichen Kategorien erhalten.

Öffnen Sie zunächst die Transactions.aspx Seite im BatchData Ordner, und ziehen Sie eine GridView aus der Toolbox auf den Designer. Legen Sie ID auf Products fest und binden Sie es über das Smart-Tag an eine neue ObjectDataSource namens ProductsDataSource. Konfigurieren Sie die ObjectDataSource so, dass sie ihre Daten aus der ProductsBLL Klasse und der GetProducts Methode zieht. Dies ist eine schreibgeschützte GridView. Legen Sie daher die Dropdownlisten in den Registerkarten UPDATE, EINFÜGEN und LÖSCHEN auf (Keine) fest, und klicken Sie auf "Fertig stellen".

Konfigurieren Sie die ObjectDataSource zur Verwendung der GetProducts-Methode der ProductsBLL-Klasse

Abbildung 5: Konfigurieren der ObjectDataSource für die Verwendung der ProductsBLL Klassenmethode GetProducts (Klicken, um das Bild in voller Größe anzuzeigen)

Legen Sie die Drop-Down Listen in den Reitern UPDATE, INSERT und DELETE auf Keine fest.

Abbildung 6: Festlegen der Drop-Down Listen in den Registerkarten UPDATE, INSERT und DELETE auf (Keine) (Klicken, um das Bild in voller Größe anzuzeigen)

Nach Abschluss des Assistenten zum Konfigurieren von Datenquellen erstellt Visual Studio "BoundFields" und ein "CheckBoxField" für die Produktdatenfelder. Entfernen Sie alle diese Felder außer ProductID, ProductName, CategoryID und CategoryName, und benennen Sie die ProductName- und CategoryName-BoundField-HeaderText-Eigenschaften in "Product" und "Category" um. Aktivieren Sie im Smarttag die Option "Paging aktivieren". Nachdem Sie diese Änderungen vorgenommen haben, sollte das deklarative Markup von GridView und ObjectDataSource wie folgt aussehen:

<asp:GridView ID="Products" runat="server" AllowPaging="True" 
    AutoGenerateColumns="False" DataKeyNames="ProductID" 
    DataSourceID="ProductsDataSource">
    <Columns>
        <asp:BoundField DataField="ProductID" HeaderText="ProductID" 
            InsertVisible="False" ReadOnly="True" 
            SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" HeaderText="Product" 
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" 
            SortExpression="CategoryID" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProducts" TypeName="ProductsBLL">
</asp:ObjectDataSource>

Fügen Sie als Nächstes drei Schaltflächen-Websteuerelemente oberhalb der GridView hinzu. Legen Sie die Text-Eigenschaft der ersten Schaltfläche auf "Gitter aktualisieren", die der zweiten auf "Kategorien ändern (MIT TRANSAKTION)" und die der dritten auf "Kategorien ändern (OHNE TRANSAKTION)" fest.

<p>
    <asp:Button ID="RefreshGrid" runat="server" Text="Refresh Grid" />
</p>
<p>
    <asp:Button ID="ModifyCategoriesWithTransaction" runat="server"
        Text="Modify Categories (WITH TRANSACTION)" />
</p>
<p>
    <asp:Button ID="ModifyCategoriesWithoutTransaction" runat="server"
        Text="Modify Categories (WITHOUT TRANSACTION)" />
</p>

An diesem Punkt sollte die Entwurfsansicht in Visual Studio dem in Abbildung 7 gezeigten Screenshot ähneln.

Die Seite enthält eine GridView- und drei Schaltflächen-Websteuerelemente.

Abbildung 7: Die Seite enthält eine GridView- und drei Schaltflächen-Websteuerelemente (Klicken, um das Bild in voller Größe anzuzeigen)

Erstellen Sie Ereignishandler für jedes der drei Button-Ereignisse Click und verwenden Sie den folgenden Code:

Protected Sub RefreshGrid_Click _
    (ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles RefreshGrid.Click
    
    Products.DataBind()
End Sub
Protected Sub ModifyCategoriesWithTransaction_Click _
    (ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles ModifyCategoriesWithTransaction.Click
    
    ' Get the set of products
    Dim productsAPI As New ProductsBLL()
    Dim productsData As Northwind.ProductsDataTable = productsAPI.GetProducts()
    ' Update each product's CategoryID
    For Each product As Northwind.ProductsRow In productsData
        product.CategoryID = product.ProductID
    Next
    ' Update the data using a transaction
    productsAPI.UpdateWithTransaction(productsData)
    ' Refresh the Grid
    Products.DataBind()
End Sub
Protected Sub ModifyCategoriesWithoutTransaction_Click _
    (ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles ModifyCategoriesWithoutTransaction.Click
    
    ' Get the set of products
    Dim productsAPI As New ProductsBLL()
    Dim productsData As Northwind.ProductsDataTable = productsAPI.GetProducts()
    ' Update each product's CategoryID
    For Each product As Northwind.ProductsRow In productsData
        product.CategoryID = product.ProductID
    Next
    ' Update the data WITHOUT using a transaction
    Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
    productsAdapter.Update(productsData)
    ' Refresh the Grid
    Products.DataBind()
End Sub

Der Ereignishandler des Aktualisieren-Buttons Click bindet die Daten einfach erneut an die GridView, indem Products GridView-Methode DataBind aufgerufen wird.

Der zweite Ereignishandler weist die Produkte CategoryID neu zu und verwendet die neue Transaktionsmethode aus der BLL, um die Datenbankaktualisierungen unter dem Dach einer Transaktion auszuführen. Beachten Sie, dass jedes Produkt CategoryID beliebig auf denselben Wert wie das produkt ProductIDfestgelegt ist. Dies funktioniert für die ersten paar Produkte einwandfrei, da diese Produkte ProductID-Werte aufweisen, die mit gültigen CategoryID übereinstimmen. Aber sobald der ProductID s zu groß wird, gilt diese zufällige Überschneidung von ProductID s und CategoryID s nicht mehr.

Der dritte Click Ereignishandler aktualisiert die Produkte CategoryID auf die gleiche Weise, sendet aber das Update mithilfe der ProductsTableAdapter Standardmethode Update an die Datenbank. Diese Update Methode umschließt nicht die Reihe von Befehlen innerhalb einer Transaktion, sodass diese Änderungen vor dem ersten aufgetretenen Fremdschlüsseleinschränkungsfehler weiterhin bestehen bleiben.

Um dieses Verhalten zu veranschaulichen, besuchen Sie diese Seite über einen Browser. Zunächst sollte die erste Seite mit Daten angezeigt werden, wie in Abbildung 8 dargestellt. Klicken Sie als Nächstes auf die Schaltfläche "Kategorien ändern" (WITH TRANSACTION). Dies führt zu einem Postback, bei dem versucht wird, alle CategoryID-Werte der Produkte zu aktualisieren, was jedoch zu einer Fremdschlüsselverletzung führt (siehe Abbildung 9).

Die Produkte werden in einer blätterbaren GridView angezeigt

Abbildung 8: Die Produkte werden in einer pageable GridView angezeigt (Zum Anzeigen des Bilds mit voller Größe klicken)

Erneutes Zuweisen der Kategorien führt zu einer Verletzung der Fremdschlüsselbedingung

Abbildung 9: Neuzuordnung der Kategorien führt zu einem Verstoß gegen die Fremdschlüsseleinschränkung (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Klicken Sie nun auf die Schaltfläche "Zurück" Ihres Browsers, und klicken Sie dann auf die Schaltfläche "Raster aktualisieren". Beim Aktualisieren der Daten sollten Sie exakt die gleiche Ausgabe wie in Abbildung 8 sehen. Das heißt, obwohl einige der Produkte CategoryID in rechtliche Werte geändert und in der Datenbank aktualisiert wurden, wurden sie zurückgesetzt, wenn die Verletzung der Fremdschlüsseleinschränkung aufgetreten ist.

Versuchen Sie nun, auf die Schaltfläche "Kategorien ändern" (OHNE TRANSAKTION) zu klicken. Dies führt zu demselben Fehler bei der Fremdschlüsseleinschränkung (siehe Abbildung 9), aber dieses Mal werden die Produkte, deren CategoryID Werte in einen gültigen Wert geändert wurden, nicht rückgängig gemacht. Klicken Sie auf die Schaltfläche "Zurück" Ihres Browsers und dann auf die Schaltfläche "Raster aktualisieren". Wie in Abbildung 10 dargestellt, wurden die CategoryID der ersten acht Produkte neu zugewiesen. In Abbildung 8 hatte Chang beispielsweise eine CategoryID von 1, aber in Abbildung 10 wurde sie auf 2 neu zugewiesen.

Einige Produkte CategoryID-Werte wurden aktualisiert, während andere nicht aktualisiert wurden

Abbildung 10: Einige Produktwerte CategoryID wurden aktualisiert, während andere nicht vorhanden waren (Klicken Sie, um das Bild in voller Größe anzuzeigen)

Zusammenfassung

Standardmäßig schließen die TableAdapter-Methoden die ausgeführten Datenbankanweisungen nicht innerhalb des Bereichs einer Transaktion um, aber mit etwas Arbeit können wir Methoden hinzufügen, die eine Transaktion erstellen, übernehmen und zurücksetzen. In diesem Lernprogramm haben wir drei solche Methoden in der ProductsTableAdapter Klasse erstellt: BeginTransaction, , CommitTransactionund RollbackTransaction. Wir haben gesehen, wie diese Methoden zusammen mit einem Try...Catch Block verwendet werden, um eine Reihe von Datenänderungsanweisungen atomar zu machen. Insbesondere haben wir die UpdateWithTransaction Methode im ProductsTableAdapter entwickelt, die das Batchaktualisierungsmuster verwendet, um die erforderlichen Änderungen an den Zeilen eines angegebenen ProductsDataTable auszuführen. Außerdem wurde die DeleteProductsWithTransaction-Methode zur ProductsBLL-Klasse in der BLL hinzugefügt, die eine List von ProductID-Werten als Eingabe entgegennimmt und die DB-Direct-Methoden-Vorlage Delete für jede ProductID aufruft. Beide Methoden erstellen zunächst eine Transaktion und führen dann die Datenänderungsanweisungen innerhalb eines Try...Catch Blocks aus. Wenn eine Ausnahme auftritt, wird die Transaktion zurückgesetzt, andernfalls wird sie bestätigt.

Schritt 5 veranschaulichte die Auswirkung von Transaktionsbatchaktualisierungen im Vergleich zu Batchaktualisierungen, die die Verwendung einer Transaktion vernachlässigt haben. In den nächsten drei Lernprogrammen bauen wir auf der Grundlage dieses Lernprogramms auf und erstellen Benutzeroberflächen zum Ausführen von Batchaktualisierungen, Löschvorgängen und Einfügungen.

Glückliche Programmierung!

Weitere Informationen

Weitere Informationen zu den in diesem Lernprogramm erläuterten Themen finden Sie in den folgenden Ressourcen:

Zum Autor

Scott Mitchell, Autor von sieben ASP/ASP.NET Büchern und Gründer von 4GuysFromRolla.com, arbeitet seit 1998 mit Microsoft Web Technologies zusammen. Scott arbeitet als unabhängiger Berater, Trainer und Schriftsteller. Sein neuestes Buch ist Sams Teach Yourself ASP.NET 2.0 in 24 Stunden. Er kann bei mitchell@4GuysFromRolla.comerreicht werden.

Besonderer Dank an

Diese Lernprogrammreihe wurde von vielen hilfreichen Prüfern überprüft. Leitende Prüfer für dieses Lernprogramm waren Dave Gardner, Hilton Giesenow und Teresa Murphy. Möchten Sie meine bevorstehenden MSDN-Artikel überprüfen? Wenn ja, schicken Sie mir eine Nachricht an mitchell@4GuysFromRolla.com.