Freigeben über


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

von Scott Mitchell

PDF herunterladen

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

Einführung

Wie wir ab dem Tutorial Eine Übersicht über das Einfügen, Aktualisieren und Löschen von Daten gesehen haben, bietet GridView integrierte Unterstützung für das Bearbeiten und Löschen auf Zeilenebene. Mit wenigen Mausklicks ist es möglich, eine umfangreiche Datenänderungsschnittstelle zu erstellen, ohne eine Codezeile zu schreiben, solange Sie mit der Bearbeitung und Löschung pro Zeile zufrieden sind. In bestimmten Szenarien ist dies jedoch unzureichend, und wir müssen Benutzern die Möglichkeit geben, einen Batch 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, wobei 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 die Schaltfläche Ausgewählte Nachrichten löschen klickt. Eine Batchbearbeitungsschnittstelle eignet sich 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, rendert eine Batchbearbeitungsoberfläche jede Zeile mit ihrer Bearbeitungsoberfläche. Der Benutzer kann den Satz von Zeilen, die geändert werden müssen, schnell ändern und diese Änderungen dann speichern, indem er auf die Schaltfläche Alle aktualisieren klickt. In diesen Tutorials wird untersucht, wie Sie Schnittstellen zum Einfügen, Bearbeiten und Löschen von Datenbatches erstellen.

Beim Ausführen von Batchvorgängen ist es wichtig zu bestimmen, ob einige der Vorgänge im Batch erfolgreich sein sollten, während andere fehlschlagen. Betrachten Sie eine Schnittstelle zum Löschen von Batchs. Was sollte passieren, wenn der erste ausgewählte Datensatz erfolgreich gelöscht wird, aber der zweite Datensatz fehlschlägt, z. B. aufgrund eines Verstoßes gegen die Fremdschlüsseleinschränkung? Sollte für das Löschen des ersten Datensatzes ein Rollback ausgeführt werden, oder ist es akzeptabel, dass der erste Datensatz gelöscht bleibt?

Wenn der Batchvorgang als atomischer Vorgang behandelt werden soll, bei dem entweder alle Schritte erfolgreich sind oder alle Schritte fehlschlagen, muss die Datenzugriffsebene erweitert werden, um Die Unterstützung für Datenbanktransaktionen zu enthalten. Datenbanktransaktionen garantieren die Atomarität für den Satz von INSERT- , UPDATE- und 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 Tutorial erfahren Sie, wie Sie die DAL erweitern, um Datenbanktransaktionen zu verwenden. In den nachfolgenden Tutorials wird die Implementierung von Webseiten für das Einfügen, Aktualisieren und Löschen von Schnittstellen im Batch untersucht. Lassen Sie uns loslegen!

Hinweis

Beim Ändern von Daten in einer Batchtransaktion ist die Atomaritä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 von 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 werden, gelöscht bleiben. In solchen Fällen muss die DAL nicht geändert werden, um Datenbanktransaktionen zu unterstützen. Es gibt jedoch andere Batchbetriebsszenarien, in denen Atomarität von entscheidender Bedeutung ist. Wenn ein Kunde sein Geld von einem Bankkonto auf ein anderes verschiebt, müssen zwei Vorgänge durchgeführt werden: Das Guthaben muss vom ersten Konto abgezogen und dann dem zweiten Konto hinzugefügt werden. Während die Bank vielleicht nichts dagegen hat, dass der erste Schritt erfolgreich ist, aber der zweite Schritt fehlschlägt, wären ihre Kunden verständlicherweise verärgert. Ich ermutige Sie, dieses Tutorial durchzuarbeiten und die Verbesserungen an der DAL zu implementieren, um Datenbanktransaktionen zu unterstützen, auch wenn Sie nicht beabsichtigen, sie in den Folgenden drei Tutorials zum Einfügen, Aktualisieren und Löschen von Batchschnittstellen zu verwenden.

Übersicht über Transaktionen

Die meisten Datenbanken bieten Unterstützung für Transaktionen, die es ermöglichen, mehrere Datenbankbefehle in einer einzigen logischen Arbeitseinheit zu gruppieren. Die Datenbankbefehle, aus denen eine Transaktion besteht, sind garantiert atomar, was bedeutet, dass entweder alle Befehle fehlschlagen oder alle erfolgreich sind.

Im Allgemeinen werden Transaktionen über SQL-Anweisungen im folgenden Muster implementiert:

  1. Geben Sie den Start einer Transaktion an.
  2. Führen Sie die SQL-Anweisungen aus, aus denen die Transaktion besteht.
  3. Wenn in einer der Anweisungen aus Schritt 2 ein Fehler auftritt, führen Sie ein Rollback für die Transaktion durch.
  4. Wenn alle Anweisungen aus Schritt 2 ohne Fehler abgeschlossen werden, committen Sie die Transaktion.

Die SQL-Anweisungen, die zum Erstellen, Committen und Rollback der Transaktion verwendet werden, können manuell eingegeben werden, wenn SQL-Skripts geschrieben oder gespeicherte Prozeduren erstellt werden, oder programmgesteuert mithilfe von ADO.NET oder den Klassen im System.Transactions Namespace. In diesem Tutorial wird nur die Verwaltung von Transaktionen mit ADO.NET untersucht. In einem zukünftigen Tutorial wird erläutert, wie gespeicherte Prozeduren auf der Datenzugriffsebene verwendet werden. Zu diesem Zeitpunkt werden die SQL-Anweisungen zum Erstellen, Rollback und Committen von Transaktionen untersucht. Weitere Informationen finden Sie in der Zwischenzeit unter Verwalten von Transaktionen in SQL Server gespeicherten Prozeduren.

Hinweis

Die TransactionScope -Klasse im System.Transactions -Namespace ermöglicht Entwicklern das programmgesteuerte Umschließen einer Reihe von Anweisungen innerhalb des Bereichs einer Transaktion und bietet Unterstützung für komplexe Transaktionen, die mehrere Quellen umfassen, z. B. zwei verschiedene Datenbanken oder sogar heterogene Typen von Datenspeichern, z. B. eine Microsoft SQL Server-Datenbank, eine Oracle-Datenbank und einen Webdienst. Ich habe mich entschieden, ADO.NET Transaktionen für dieses Tutorial anstelle der TransactionScope -Klasse zu verwenden, da ADO.NET für Datenbanktransaktionen spezifischer und in vielen Fällen viel weniger ressourcenintensiv ist. Darüber hinaus verwendet die TransactionScope Klasse in 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, das den Rahmen dieser Tutorials übersteigt.

Wenn Sie mit dem SqlClient-Anbieter in ADO.NET arbeiten, werden Transaktionen durch einen Aufruf der s-MethodeBeginTransaction der SqlConnection Klasse initiiert, die ein SqlTransaction -Objekt zurückgibt. Die Datenänderungsanweisungen, die die Transaktion bilden, werden innerhalb eines try...catch Blocks platziert. Wenn in einer Anweisung im Block ein Fehler auftritt, wird die try Ausführung an den catch Block übertragen, in dem die Transaktion über die Methode sRollback desSqlTransaction Objekts zurückgesetzt werden kann. Wenn alle Anweisungen erfolgreich abgeschlossen wurden, wird die Transaktion durch einen Aufruf der SqlTransaction Objekt-s-Methode Commit am Ende des try Blocks committet. 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 typisierten 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 innerhalb des Bereichs einer Transaktion auszuführen. In Schritt 2 erfahren Sie, wie Sie partielle Klassen verwenden, um diese Methoden hinzuzufügen.

Schritt 1: Erstellen der Webseiten zum Arbeiten mit Batchdaten

Bevor wir mit der Erweiterung des DAL zur Unterstützung von Datenbanktransaktionen beginnen, nehmen wir uns zunächst einen Moment Zeit, um die ASP.NET Webseiten zu erstellen, die wir für dieses Tutorial 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 Sie jede Seite der Site.master master Seite zuordnen.

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

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

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

Wie bei den anderen Ordnern wird das SectionLevelTutorialListing.ascx Benutzersteuerelement verwendet, Default.aspx um die Tutorials innerhalb des zugehörigen Abschnitts auflisten zu können. Fügen Sie daher dieses Benutzersteuerelement zu Default.aspx hinzu, indem Sie es vom Projektmappen-Explorer in die Entwurfsansicht der Seite ziehen.

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

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

Fügen Sie schließlich diese vier Seiten als Einträge zur Web.sitemap Datei hinzu. Fügen Sie insbesondere nach dem Anpassen der Siteübersicht <siteMapNode>das folgende Markup 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.sitemapeinen Moment Zeit, um die Tutorials-Website über einen Browser anzuzeigen. Das Menü auf der linken Seite enthält nun Elemente für das Arbeiten mit Batchdatentutorials.

Die Siteübersicht enthält jetzt Einträge für die Tutorials zum Arbeiten mit Batchdaten.

Abbildung 3: Die Siteübersicht enthält jetzt Einträge für die Tutorials zum Arbeiten mit Batchdaten.

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

Wie bereits im ersten Tutorial beschrieben, erstellen einer Datenzugriffsebene, besteht das typisierte DataSet in unserer 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, zum Aktualisieren der Datenbank mit änderungen an den DataTables usw. bereitstellen. Denken Sie daran, dass die TableAdapters zwei Muster zum Aktualisieren von Daten bereitstellen, die ich als Batchupdate und DB-Direct bezeichnet habe. Mit dem Batchupdatemuster wird dem TableAdapter ein DataSet, eine DataTable oder eine Sammlung von DataRows übergeben. Diese Daten werden aufgezählt, und für jede eingefügte, geänderte oder gelöschte Zeile wird , InsertCommandUpdateCommandoder DeleteCommand ausgeführt. Beim DB-Direct-Muster wird dem 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 diese übergebenen Werte, um die entsprechende InsertCommandAnweisung , UpdateCommandoder DeleteCommand auszuführen.

Unabhängig vom verwendeten Aktualisierungsmuster verwenden die automatisch generierten TableAdapters-Methoden keine Transaktionen. Standardmäßig wird jeder vom TableAdapter ausgeführte Einfüge-, Aktualisierungs- oder Löschvorgang als einzelner diskreter Vorgang behandelt. Stellen Sie sich für instance vor, dass das DB-Direct-Muster von code in der BLL verwendet wird, um zehn Datensätze in die Datenbank einzufügen. Dieser Code würde die TableAdapter-Methode Insert zehnmal aufrufen. Wenn die ersten fünf Einfügungen erfolgreich waren, die sechste jedoch zu einer Ausnahme führte, verbleiben die ersten fünf eingefügten Datensätze in der Datenbank. Wenn das Batchupdatemuster verwendet wird, um Einfügungen, Aktualisierungen und Löschvorgänge an den eingefügten, geänderten und gelöschten Zeilen in einer DataTable auszuführen, und wenn die ersten Änderungen erfolgreich waren, aber später ein Fehler aufgetreten ist, verbleiben diese früheren Änderungen, die abgeschlossen wurden, in der Datenbank.

In bestimmten Szenarien möchten wir die Atomarität über eine Reihe von Modifikationen hinweg sicherstellen. Um dies zu erreichen, müssen wir den TableAdapter manuell erweitern, indem wir neue Methoden hinzufügen, die die InsertCommand, UpdateCommandund DeleteCommand s unter dem Dach einer Transaktion ausführen. In Erstellen einer Datenzugriffsebene haben wir die Verwendung von partiellen Klassen untersucht, um die Funktionalität der DataTables innerhalb des typisierten DataSets zu erweitern. Diese Technik kann auch mit TableAdapters verwendet werden.

Das typisierte DataSet Northwind.xsd befindet sich im App_Code Ordner s-Unterordner 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 enthält die partielle Implementierung von , die ProductsTableAdapter Methoden zum Durchführen von Datenänderungen mithilfe einer Transaktion enthält.

Fügen Sie einen Ordner namens TransactionSupport und eine Klassendatei mit dem Namen ProductsTableAdapter.TransactionSupport.vb

Abbildung 4: Hinzufügen eines Ordners namens TransactionSupport und einer Klassendatei namens 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

Die Partial Schlüsselwort (keyword) in der Klassendeklaration gibt dem Compiler an, dass die darin hinzugefügten Member der ProductsTableAdapter Klasse im NorthwindTableAdapters Namespace 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 er intern ein SqlDataAdapter -Objekt, um seine Befehle an die Datenbank auszugeben. Daher müssen wir die SqlTransaction -Klasse verwenden, um die Transaktion zu starten und sie dann zu committen oder rückgängig zu machen. 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 als gekennzeichnet Public, sodass sie innerhalb von ProductsTableAdapter, aus einer anderen Klasse im DAL oder von einer anderen Ebene in der Architektur, z. B. der 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 s-Objekte SqlCommand an. CommitTransaction und RollbackTransaction rufen die Transaction Objektmethoden s Commit bzw. auf Rollback , bevor das interne Connection Objekt geschlossen wird.

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

Wenn diese Methoden abgeschlossen sind, sind wir bereit, Methoden ProductsDataTable zu oder der BLL hinzuzufügen, die eine Reihe von Befehlen unter dem Dach einer Transaktion ausführen. Die folgende Methode verwendet das Batchupdatemuster, um eine ProductsDataTable instance mithilfe einer Transaktion zu aktualisieren. Es startet eine Transaktion durch Aufrufen der BeginTransaction -Methode und verwendet dann einen Try...Catch -Block zum Ausgeben der Datenänderungsanweisungen. Wenn der Aufruf der Methode des AdapterUpdate Objekts zu einer Ausnahme führt, wird die Ausführung an den catch Block übertragen, in dem für die Transaktion ein Rollback ausgeführt und die Ausnahme erneut ausgelöst wird. Denken Sie daran, dass die Update -Methode das Batchupdatemuster implementiert, indem die Zeilen der angegebenen ProductsDataTable aufgezählt und die erforderlichen InsertCommand, UpdateCommandund DeleteCommand s ausgeführt werden. Wenn einer dieser Befehle zu einem Fehler führt, wird für die Transaktion ein Rollback ausgeführt, wodurch die vorherigen Änderungen rückgängig gemacht werden, die während der Lebensdauer der Transaktion vorgenommen wurden. Wenn die Update Anweisung ohne Fehler abgeschlossen wird, wird ein Commit für die Transaktion vollständig ausgefü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 der -Klasse die UpdateWithTransactionProductsTableAdapter -Methode über die partielle Klasse in ProductsTableAdapter.TransactionSupport.vbhinzu. Alternativ kann diese Methode der Business Logic Layer-Klasse ProductsBLL mit einigen geringfügigen syntaktischen Änderungen hinzugefügt werden. Die Schlüsselwort (keyword) in , und müsste durch Adapter ersetzt werden (denken Sie daran, dass Adapter der Name einer Eigenschaft vom ProductsBLL Typ ProductsTableAdapterist).Me.RollbackTransaction()Me.CommitTransaction()Me.BeginTransaction()Me

Die UpdateWithTransaction -Methode verwendet das Batch Update-Muster, 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 eine List(Of T) vom Typ Integer, bei der es sich um die ProductID zu löschenden s handelt. Die -Methode initiiert die Transaktion über einen Aufruf BeginTransaction von und durchläuft dann im Try -Block die angegebene Liste und ruft die DB-Direct-Mustermethode Delete für jeden ProductID Wert auf. Wenn einer der Aufrufe von Delete fehlschlägt, wird die Steuerung an den Catch Block übertragen, in dem für die Transaktion ein Rollback ausgeführt und die Ausnahme erneut ausgelöst wird. Wenn alle Aufrufe erfolgreich sind Delete , wird ein Commit für die Transaktion ausgeführt. 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 transaktionsbezogene Code, der in diesem Tutorial untersucht wird, ermöglicht, dass mehrere Anweisungen für den ProductsTableAdapter als atomischer Vorgang behandelt werden. Was aber, wenn mehrere Änderungen an verschiedenen Datenbanktabellen atomar durchgeführt werden müssen? Für instance sollten wir beim Löschen einer Kategorie zunächst die aktuellen Produkte einer anderen Kategorie zuweisen. Diese beiden Schritte, die die Produkte neu zuweisen und die Kategorie löschen, sollten als atomischer Vorgang ausgeführt werden. ProductsTableAdapter Die enthält jedoch nur Methoden zum Ändern der Products Tabelle, und die CategoriesTableAdapter enthält nur Methoden zum Ändern der Categories Tabelle. Wie kann eine Transaktion also beide TableAdapters umfassen?

Eine Möglichkeit besteht darin, dem CategoriesTableAdapter benannten DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID) eine Methode hinzuzufügen und diese Methode eine gespeicherte Prozedur aufrufen zu lassen, die die Produkte neu zuweisen und die Kategorie innerhalb des Bereichs einer Transaktion löscht, die innerhalb der gespeicherten Prozedur definiert ist. In einem zukünftigen Tutorial erfahren Sie, wie Sie Transaktionen in gespeicherten Prozeduren starten, committen und rollbacken.

Eine weitere Möglichkeit besteht darin, eine Hilfsklasse im DAL zu erstellen, die die DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID) -Methode enthält. Diese Methode erstellt eine instance der CategoriesTableAdapter und der ProductsTableAdapter und legt dann diese beiden TableAdapters-Eigenschaften Connection auf dieselbe SqlConnection instance fest. An diesem Punkt würde einer der beiden TableAdapters die Transaktion mit einem Aufruf von BeginTransactioninitiieren. Die TableAdapters-Methoden zum Erneuten Zuweisen der Produkte und zum Löschen der Kategorie werden in einem Try...Catch Block aufgerufen, wobei die Transaktion bei Bedarf committet oder ein Rollback ausgeführt wird.

Schritt 4: Hinzufügen derUpdateWithTransactionMethode zur Geschäftslogikebene

In Schritt 3 haben wir dem ProductsTableAdapter im DAL eine UpdateWithTransaction -Methode hinzugefügt. Wir sollten der BLL eine entsprechende Methode hinzufügen. Während die Präsentationsebene direkt zum DAL aufrufen könnte, um die UpdateWithTransaction -Methode aufzurufen, haben diese Tutorials versucht, eine mehrschichtige Architektur zu definieren, die das DAL von der Präsentationsebene isoliert. Daher ist es uns wichtig, diesen Ansatz fortzusetzen.

Öffnen Sie die ProductsBLL Klassendatei, und fügen Sie eine Methode mit dem Namen UpdateWithTransaction hinzu, die einfach die entsprechende DAL-Methode aufruft. Es sollten nun zwei neue Methoden in ProductsBLLvorhanden sein: UpdateWithTransaction, die Sie gerade hinzugefügt haben, und DeleteProductsWithTransaction, das 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 DataObjectMethodAttribute meisten anderen Methoden in der ProductsBLL -Klasse zugewiesen ist, da wir diese Methoden direkt aus der ASP.NET CodeBehind-Klassen aufrufen. Denken Sie daran, dass DataObjectMethodAttribute verwendet wird, um zu kennzeichnen, welche Methoden im Assistenten zum Konfigurieren von Datenquellen von ObjectDataSource und auf welcher Registerkarte (SELECT, UPDATE, INSERT oder DELETE) angezeigt werden sollen. Da 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: Atomares Aktualisieren von Datenbankdaten von der Präsentationsebene

Um den Effekt zu veranschaulichen, den die Transaktion beim Aktualisieren eines Datensatzbatches hat, erstellen wir eine Benutzeroberfläche, die alle Produkte in einer GridView auflistet und ein Button-Websteuerelement enthält, das die Produktwerte CategoryID neu zuteilt, wenn darauf geklickt wird. Insbesondere wird die Kategorieneuzuweisung fortgesetzt, 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, das CategoryID nicht mit einer vorhandenen Kategorie übereinstimmt CategoryID, tritt ein Verstoß gegen die Fremdschlüsseleinschränkung auf, und es wird eine Ausnahme ausgelöst. In diesem Beispiel sehen wir, dass bei Verwendung einer Transaktion die ausnahme, die von der Verletzung der Fremdschlüsseleinschränkung ausgelöst wird, dazu führt, dass die vorherigen gültigen CategoryID Änderungen zurückgesetzt werden. Wenn Sie keine Transaktion verwenden, bleiben die Änderungen an den ursprünglichen Kategorien jedoch erhalten.

Öffnen Sie zunächst die Transactions.aspx Seite im BatchData Ordner, und ziehen Sie eine GridView aus der Toolbox auf die Designer. Legen Sie auf IDProducts fest, und binden Sie es über das Smarttag an eine neue ObjectDataSource mit dem Namen ProductsDataSource. Konfigurieren Sie die ObjectDataSource so, dass ihre Daten aus der s-Methode der ProductsBLL Klasse GetProducts abgerufen werden. Dies ist ein schreibgeschütztes GridView. Legen Sie daher die Dropdownlisten in den Registerkarten UPDATE, INSERT und DELETE auf (Keine) fest, und klicken Sie auf Fertig stellen.

Konfigurieren der ObjectDataSource für die Verwendung der GetProducts-Methode der ProductsBLL-Klasse

Abbildung 5: Konfigurieren der ObjectDataSource für die Verwendung der ProductsBLL Class s-Methode (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)GetProducts

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

Abbildung 6: Festlegen der Drop-Down Listen in den Registerkarten UPDATE, INSERT und DELETE auf (Keine) (Klicken Sie hier, 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 mit Ausnahme ProductIDvon , CategoryIDProductName, und CategoryName benennen Sie die ProductName Eigenschaften und CategoryName boundFields HeaderText in Product bzw. 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 Button-Websteuerelemente oberhalb von GridView hinzu. Legen Sie die erste Text-Eigenschaft von Button auf Refresh Grid fest, die zweite auf Modify Categories (WITH TRANSACTION) und die dritte auf Modify Categories (WITHOUT TRANSACTION).

<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 in etwa wie in Abbildung 7 dargestellt aussehen.

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

Abbildung 7: Die Seite enthält ein GridView-Steuerelement und drei Schaltflächen-Websteuerelemente (Klicken Sie hier, 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 Refresh Button s-Ereignishandler Click bindet die Daten einfach neu an gridView, indem die Products GridView-Methode aufgerufen wird DataBind .

Der zweite Ereignishandler zuweisen die Produkte CategoryID neu und verwendet die neue Transaktionsmethode aus der BLL, um die Datenbankupdates unter dem Dach einer Transaktion durchzuführen. Beachten Sie, dass jedes Produkt s CategoryID willkürlich auf denselben Wert wie sein ProductIDfestgelegt wird. Dies funktioniert für die ersten Produkte gut, da diese Produkte Werte aufweisen ProductID , die den gültigen CategoryID s zugeordnet werden. Aber sobald die ProductID s zu groß werden, gilt diese zufällige Überlappung von ProductID s und CategoryID s nicht mehr.

Der dritte Click Ereignishandler aktualisiert die Produkte CategoryID auf die gleiche Weise, sendet das Update jedoch mithilfe der Standardmethode Update s an die ProductsTableAdapter Datenbank. Diese Update Methode umschließt nicht die Reihe von Befehlen innerhalb einer Transaktion, sodass diese Änderungen vorgenommen werden, bevor der erste aufgetretene Fremdschlüsseleinschränkungsfehler weiterhin auftritt.

Um dieses Verhalten zu veranschaulichen, besuchen Sie diese Seite über einen Browser. Zunächst sollte die erste Seite der 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 und dem Versuch, alle Produktwerte CategoryID zu aktualisieren, führt jedoch zu einer Verletzung der Fremdschlüsseleinschränkung (siehe Abbildung 9).

Die Produkte werden in einer pageable GridView angezeigt.

Abbildung 8: Die Produkte werden in einem pageable GridView angezeigt (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Das Erneute Zuweisen der Kategorien führt zu einer Verletzung der Fremdschlüsseleinschränkung

Abbildung 9: Erneutes Zuweisen der Kategorien führt zu einer Verletzung der Fremdschlüsseleinschränkung (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Klicken Sie nun auf die Schaltfläche Zurück in Ihrem Browser, und klicken Sie dann auf die Schaltfläche Raster aktualisieren. Beim Aktualisieren der Daten sollte genau die gleiche Ausgabe wie in Abbildung 8 angezeigt werden. Das heißt, obwohl einige der Produkte CategoryID in gesetzliche Werte geändert und in der Datenbank aktualisiert wurden, wurden sie zurückgesetzt, als die Fremdschlüsseleinschränkung verletzt wurde.

Versuchen Sie nun, auf die Schaltfläche Kategorien ändern (OHNE TRANSAKTION) zu klicken. Dies führt zu demselben Fremdschlüsseleinschränkungsfehler (siehe Abbildung 9), aber dieses Mal wird für Produkte, deren Werte in einen gesetzlichen CategoryID Wert geändert wurden, kein Rollback ausgeführt. Klicken Sie auf die Schaltfläche Zurück in Ihrem Browser und dann auf die Schaltfläche Raster aktualisieren. Wie Abbildung 10 zeigt, wurden die CategoryID s der ersten acht Produkte neu zugewiesen. In Abbildung 8 hatte Chang beispielsweise einen CategoryID von 1, aber in Abbildung 10 wurde er 2 zugewiesen.

Einige Products CategoryID-Werte wurden aktualisiert, während andere nicht waren.

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

Zusammenfassung

Standardmäßig umschließen die TableAdapter-Methoden die ausgeführten Datenbankanweisungen nicht innerhalb des Bereichs einer Transaktion, aber mit wenig Aufwand können wir Methoden hinzufügen, die eine Transaktion erstellen, committen und rollbacken. In diesem Tutorial 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 in erstellt ProductsTableAdapter, die das Batch Update-Muster verwendet, um die erforderlichen Änderungen an den Zeilen eines angegebenen ProductsDataTableauszuführen. Wir haben die DeleteProductsWithTransaction -Methode auch der ProductsBLL -Klasse in der BLL hinzugefügt, die eine List von ProductID -Werten als Eingabe akzeptiert und die DB-Direct-Mustermethode Delete für jede ProductIDaufruft. Beide Methoden beginnen mit dem Erstellen einer Transaktion und dann der Ausführung der Datenänderungsanweisungen in einem Try...Catch Block. Wenn eine Ausnahme auftritt, wird für die Transaktion ein Rollback ausgeführt, andernfalls wird ein Commit ausgeführt.

Schritt 5 veranschaulichte die Auswirkungen von Transaktionsbatchupdates im Vergleich zu Batchupdates, bei denen die Verwendung einer Transaktion vernachlässigt wurde. In den nächsten drei Tutorials werden wir auf der Grundlage dieses Tutorials aufbauen und Benutzeroberflächen zum Ausführen von Batchupdates, Löschvorgängen und Einfügevorgängen erstellen.

Viel Spaß beim Programmieren!

Weitere Informationen

Weitere Informationen zu den in diesem Tutorial 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-Webtechnologien. Scott arbeitet als unabhängiger Berater, Trainer und Autor. Sein neuestes Buch ist Sams Teach Yourself ASP.NET 2.0 in 24 Stunden. Er kann unter mitchell@4GuysFromRolla.comoder über seinen Blog erreicht werden, der unter http://ScottOnWriting.NETzu finden ist.

Besonderen Dank an

Diese Tutorialreihe wurde von vielen hilfreichen Prüfern überprüft. Leitende Prüfer für dieses Tutorial waren Dave Gardner, Hilton Giesenow und Teresa Murphy. Möchten Sie meine anstehenden MSDN-Artikel lesen? Wenn dies der Fall ist, legen Sie eine Zeile unter abmitchell@4GuysFromRolla.com.