Umschließen von Datenbankänderungen innerhalb einer Transaktion (VB)
von Scott Mitchell
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:
- Geben Sie den Start einer Transaktion an.
- Führen Sie die SQL-Anweisungen aus, aus denen die Transaktion besteht.
- Wenn in einer der Anweisungen aus Schritt 2 ein Fehler auftritt, führen Sie ein Rollback für die Transaktion durch.
- 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
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.
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.sitemap
einen 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.
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 , InsertCommand
UpdateCommand
oder 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 InsertCommand
Anweisung , UpdateCommand
oder 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
, UpdateCommand
und 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.
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 Adapter
Update
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
, UpdateCommand
und 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 UpdateWithTransaction
ProductsTableAdapter
-Methode über 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. Die Schlüsselwort (keyword) in , und müsste durch Adapter
ersetzt werden (denken Sie daran, dass Adapter
der Name einer Eigenschaft vom ProductsBLL
Typ ProductsTableAdapter
ist).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 BeginTransaction
initiieren. 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 derUpdateWithTransaction
Methode 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 ProductsBLL
vorhanden 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 ID
Products
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.
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
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 ProductID
von , CategoryID
ProductName
, 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.
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 ProductID
festgelegt 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).
Abbildung 8: Die Produkte werden in einem pageable GridView angezeigt (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
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.
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
, CommitTransaction
und 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 ProductsDataTable
auszufü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 ProductID
aufruft. 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:
- Transaktionen leicht gemacht:
System.Transactions
- TransactionScope und DataAdapters
- Verwenden von Oracle-Datenbanktransaktionen in .NET
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.