Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
von Scott Mitchell
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
, 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 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:
- Geben Sie den Beginn einer Transaktion an.
- Führen Sie die SQL-Anweisungen aus, die die Transaktion umfassen.
- Wenn in einer der Anweisungen aus Schritt 2 ein Fehler auftritt, führen Sie ein Rollback der Transaktion durch.
- 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
KlassenmethodeBeginTransaction
initiiert, 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 Rollback
zurü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
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.
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.
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
, UpdateCommand
oder 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
, UpdateCommand
oder 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.
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 derUpdateWithTransaction
Methode 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".
Abbildung 5: Konfigurieren der ObjectDataSource für die Verwendung der ProductsBLL
Klassenmethode GetProducts
(Klicken, um das Bild in voller Größe anzuzeigen)
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.
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 ProductID
festgelegt 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).
Abbildung 8: Die Produkte werden in einer pageable GridView angezeigt (Zum Anzeigen des Bilds mit voller Größe klicken)
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.
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
, , 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 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:
-
Leicht gemachte Transaktionen:
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 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.