Umschließen von Datenbankänderungen innerhalb einer Transaktion (C#)
von Scott Mitchell
Dieses Lernprogramm ist der erste von vier, der 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.
Einführung
Wie wir mit dem Lernprogramm "Einfügen", "Aktualisieren" und "Löschen von Daten " begonnen haben, bietet GridView integrierte Unterstützung für die Bearbeitung und Löschung auf Zeilenebene. Mit wenigen Mausklicks ist es möglich, eine umfangreiche Datenänderungsschnittstelle zu erstellen, ohne eine Codezeile zu schreiben, solange Sie Inhalte mit der Bearbeitung und Löschung pro Zeile 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 zu erzwingen, dass der Benutzer auf "Bearbeiten" klickt, seine Änderung vornehmen und dann für jeden datensatz, der geändert werden muss, rendert eine Batchbearbeitungsschnittstelle jede Zeile mit ihrer Bearbeitungsschnittstelle. 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. Obwohl die Bank nicht daran denken mag, den ersten Schritt erfolgreich zu haben, aber der zweite Schritt fehlschlägt, würden ihre Kunden verständlicherweise verärgert sein. 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, übernehmen Sie die Transaktion.
Die SQL-Anweisungen, die zum Erstellen, Commit und Zurücksetzen der Transaktion verwendet werden, können manuell eingegeben werden, wenn SQL-Skripts geschrieben oder gespeicherte Prozeduren erstellt werden, oder über programmgesteuerte Mittel, indem sie entweder ADO.NET oder die Klassen im System.Transactions
Namespace verwenden. In diesem Lernprogramm untersuchen wir nur die Verwaltung von Transaktionen mit ADO.NET. In einem zukünftigen Lernprogramm befassen wir uns mit der Verwendung gespeicherter Prozeduren in der Datenzugriffsebene, zu dem wir die SQL-Anweisungen zum Erstellen, Zurücksetzen und Commit von Transaktionen untersuchen.
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 KlassenmethodeBeginTransaction
initiiert, die ein SqlTransaction
Objekt zurückgibt.SqlConnection
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 commits ausgeführt. Der folgende Codeausschnitt veranschaulicht dieses Muster. Siehe Verwalten der Datenbankkonsistenz mit Transaktionen.
// Create the SqlTransaction object
SqlTransaction myTransaction = 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;
}
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 Arbeit mit Batchdatenwebseiten
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 sqlDataSource-bezogene Lernprogramme
Wie bei den anderen Ordnern wird das SectionLevelTutorialListing.ascx
Benutzersteuerelement verwendet, Default.aspx
um die Lernprogramme im Abschnitt auflisten. Fügen Sie daher dieses Benutzersteuerelement Default.aspx
hinzu, indem Sie es aus der Projektmappen-Explorer in 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 Websiteübersicht <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 Lernprogramme-Website über einen Browser anzuzeigen. Das Menü auf der linken Seite enthält jetzt Elemente für die Arbeit mit Batchdatenlernlern.
Abbildung 3: Die Websiteübersicht enthält jetzt Einträge für die Arbeit mit Batchdatenlernlern
Schritt 2: Aktualisieren der Datenzugriffsebene zur Unterstützung von Datenbanktransaktionen
Wie wir im ersten Lernprogramm erläutert haben, besteht das typierte 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 wird "TableAdapter" an ein DataSet- oder DataTable-Objekt oder eine Sammlung von DataRows übergeben. 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 die eingefügten, geänderten und gelöschten Zeilen in einer DataTable durchzuführen, bleiben diese früheren Änderungen in der Datenbank erhalten, wenn die ersten änderungen erfolgreich waren, aber später ein Fehler aufgetreten ist.
In bestimmten Szenarien möchten wir die Atomität in einer Reihe von Modifikationen sicherstellen. Dazu müssen wir das TableAdapter manuell erweitern, indem neue Methoden hinzugefügt werden, die die InsertCommand
, UpdateCommand
und DeleteCommand
die unter dem Dach einer Transaktion ausgeführt werden. 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.cs
(siehe Abbildung 4). Diese Datei enthält die partielle Implementierung der ProductsTableAdapter
Datenänderungen mithilfe einer Transaktion.
Abbildung 4: Hinzufügen eines Ordners namens TransactionSupport
und einer Klassendatei mit dem Namen ProductsTableAdapter.TransactionSupport.cs
Geben Sie den folgenden Code in die ProductsTableAdapter.TransactionSupport.cs
Datei ein:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace NorthwindTableAdapters
{
public partial class ProductsTableAdapter
{
private SqlTransaction _transaction;
private SqlTransaction Transaction
{
get
{
return this._transaction;
}
set
{
this._transaction = value;
}
}
public void BeginTransaction()
{
// Open the connection, if needed
if (this.Connection.State != ConnectionState.Open)
this.Connection.Open();
// Create the transaction and assign it to the Transaction property
this.Transaction = this.Connection.BeginTransaction();
// Attach the transaction to the Adapters
foreach (SqlCommand command in this.CommandCollection)
{
command.Transaction = this.Transaction;
}
this.Adapter.InsertCommand.Transaction = this.Transaction;
this.Adapter.UpdateCommand.Transaction = this.Transaction;
this.Adapter.DeleteCommand.Transaction = this.Transaction;
}
public void CommitTransaction()
{
// Commit the transaction
this.Transaction.Commit();
// Close the connection
this.Connection.Close();
}
public void RollbackTransaction()
{
// Rollback the transaction
this.Transaction.Rollback();
// Close the connection
this.Connection.Close();
}
}
}
Das partial
Schlüsselwort in der hier aufgeführten Klassendeklaration gibt an, dass der Compiler, in dem die hinzugefügten Member der ProductsTableAdapter
Klasse im NorthwindTableAdapters
Namespace hinzugefügt werden sollen. Notieren Sie sich die using 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 übernehmen 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 von innerhalb der ProductsTableAdapter
, von einer anderen Klasse im DAL oder von einer anderen Ebene in der Architektur verwendet werden können, z. B. die BLL. 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 Sie die Transaction
Objekte Commit
bzw Rollback
. Methoden 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, Methoden oder ProductsDataTable
die BLL 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 die Zeilen der bereitgestellten ProductsDataTable
und die erforderlichen InsertCommand
, UpdateCommand
und DeleteCommand
s aufgezählt werden. 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 zugesichert.
public int UpdateWithTransaction(Northwind.ProductsDataTable dataTable)
{
this.BeginTransaction();
try
{
// Perform the update on the DataTable
int returnValue = this.Adapter.Update(dataTable);
// If we reach here, no errors, so commit the transaction
this.CommitTransaction();
return returnValue;
}
catch
{
// If we reach here, there was an error, so rollback the transaction
this.RollbackTransaction();
throw;
}
}
Fügen Sie die UpdateWithTransaction
Methode der ProductsTableAdapter
Klasse über die partielle Klasse in ProductsTableAdapter.TransactionSupport.cs
. Alternativ kann diese Methode der Business Logic Layer-Klasse ProductsBLL
mit einigen geringfügigen syntaktischen Änderungen hinzugefügt werden. Das Schlüsselwort in this.BeginTransaction()
, , this.CommitTransaction()
und this.RollbackTransaction()
muss durch Adapter
ersetzt werden (denken Sie daran, dass Adapter
es sich um den Namen einer Eigenschaft des ProductsBLL
Typs ProductsTableAdapter
handelt).
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 eines List<T>
Typs int
, die die ProductID
zu löschenden Elemente sind. Die Methode initiiert die Transaktion über einen Aufruf BeginTransaction
und durchläuft dann im try
Block die angegebene Liste, die die DB-Direct-Mustermethode Delete
für jeden ProductID
Wert aufruft. Wenn eines der Aufrufe Delete
fehlschlägt, wird die Steuerung an den Block übertragen, in dem catch
die Transaktion zurückgesetzt wird und die Ausnahme erneut ausgelöst wird. Wenn alle Aufrufe erfolgreich ausgeführt Delete
werden sollen, wird die Transaktion zugesichert. Fügen Sie diese Methode der ProductsBLL
Klasse hinzu.
public void DeleteProductsWithTransaction
(System.Collections.Generic.List<int> productIDs)
{
// Start the transaction
Adapter.BeginTransaction();
try
{
// Delete each product specified in the list
foreach (int productID in productIDs)
{
Adapter.Delete(productID);
}
// Commit the transaction
Adapter.CommitTransaction();
}
catch
{
// There was an error - rollback the transaction
Adapter.RollbackTransaction();
throw;
}
}
Anwenden von Transaktionen auf mehrere TableAdapters
Der in diesem Lernprogramm untersuchte transaktionsbezogene Code ermöglicht es, mehrere Anweisungen für die ProductsTableAdapter
Behandlung als Atomoperation zu ermöglichen. 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, der CategoriesTableAdapter
benannten DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)
Methode eine Methode hinzuzufügen und diese Methode eine gespeicherte Prozedur aufzurufen, die die Produkte neu zuzuweisen und die Kategorie innerhalb des Bereichs einer in der gespeicherten Prozedur definierten Transaktion löscht. 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 erstellt eine Instanz der CategoriesTableAdapter
und der ProductsTableAdapter
beiden TableAdapters-Eigenschaften Connection
auf dieselbe SqlConnection
Instanz. An diesem Punkt würde einer der beiden TableAdapters die Transaktion mit einem Aufruf initiieren BeginTransaction
. Die TableAdapters-Methoden zum Erneuten Zuweisen der Produkte und Löschen der Kategorie würden bei Bedarf in einem try...catch
Block aufgerufen, bei dem die Transaktion zugesichert oder zurückgesetzt wurde.
Schritt 4: Hinzufügen derUpdateWithTransaction
Methode zur Geschäftslogikebene
In Schritt 3 haben wir der ProductsTableAdapter
DAL eine UpdateWithTransaction
Methode hinzugefügt. Wir sollten der BLL eine entsprechende Methode hinzufügen. Während die Präsentationsschicht direkt auf den DAL-Aufruf der Methode aufruft UpdateWithTransaction
, haben diese Lernprogramme versucht, eine mehrschichtige Architektur zu definieren, die die DAL von der Präsentationsebene isoliert. Daher ist es uns bewölbt, diesen Ansatz fortzusetzen.
Öffnen Sie die ProductsBLL
Klassendatei, und fügen Sie eine Methode hinzu UpdateWithTransaction
, die einfach die entsprechende DAL-Methode aufruft. Es sollten nun zwei neue Methoden in ProductsBLL
: UpdateWithTransaction
, die Sie soeben hinzugefügt haben, und DeleteProductsWithTransaction
, die in Schritt 3 hinzugefügt wurde.
public int UpdateWithTransaction(Northwind.ProductsDataTable products)
{
return Adapter.UpdateWithTransaction(products);
}
public void DeleteProductsWithTransaction
(System.Collections.Generic.List<int> productIDs)
{
// Start the transaction
Adapter.BeginTransaction();
try
{
// Delete each product specified in the list
foreach (int productID in productIDs)
Adapter.Delete(productID);
// Commit the transaction
Adapter.CommitTransaction();
}
catch
{
// There was an error - rollback the transaction
Adapter.RollbackTransaction();
throw;
}
}
Hinweis
Diese Methoden enthalten nicht das Attribut, das DataObjectMethodAttribute
den meisten anderen Methoden in der ProductsBLL
Klasse zugewiesen ist, da wir diese Methoden direkt aus den codeBehind-Klassen der ASP.NET Seiten aufrufen. Erinnern Sie sich daran, dass verwendet wird, um zu kennzeichnen, DataObjectMethodAttribute
welche Methoden im Assistenten zum Konfigurieren von Datenquellen von 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 Schaltflächenweb-Steuerelement enthält, das beim Klicken die Produktwerte CategoryID
neu zuzuweisen. 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 den zugehörigen zugehörigen ID
Products
Smarttag fest, und binden Sie ihn an eine neue ObjectDataSource mit dem Namen ProductsDataSource
. Konfigurieren Sie objectDataSource, um die Daten aus der ProductsBLL
Klassenmethode GetProducts
abzurufen. 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 Dropdownlisten 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 den ProductID
Eigenschaften " CategoryID
ProductName
CategoryName
Product" bzw. "Category", und benennen Sie die ProductName
Eigenschaften "CategoryName
Product" bzw. "BoundFieldsHeaderText
" 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 Schaltflächen-Websteuerelemente oberhalb der GridView hinzu. Legen Sie die erste Text-Eigenschaft der Schaltfläche auf "Refresh Grid", die zweiten auf "Kategorien ändern" (WITH TRANSACTION) und die dritte auf "Kategorien ändern" (WITHOUT TRANSACTION) 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 void RefreshGrid_Click(object sender, EventArgs e)
{
Products.DataBind();
}
protected void ModifyCategoriesWithTransaction_Click(object sender, EventArgs e)
{
// Get the set of products
ProductsBLL productsAPI = new ProductsBLL();
Northwind.ProductsDataTable products = productsAPI.GetProducts();
// Update each product's CategoryID
foreach (Northwind.ProductsRow product in products)
{
product.CategoryID = product.ProductID;
}
// Update the data using a transaction
productsAPI.UpdateWithTransaction(products);
// Refresh the Grid
Products.DataBind();
}
protected void ModifyCategoriesWithoutTransaction_Click(object sender, EventArgs e)
{
// Get the set of products
ProductsBLL productsAPI = new ProductsBLL();
Northwind.ProductsDataTable products = productsAPI.GetProducts();
// Update each product's CategoryID
foreach (Northwind.ProductsRow product in products)
{
product.CategoryID = product.ProductID;
}
// Update the data WITHOUT using a transaction
NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
new NorthwindTableAdapters.ProductsTableAdapter();
productsAdapter.Update(products);
// Refresh the Grid
Products.DataBind();
}
Der Ereignishandler der Aktualisierungsschaltfläche Click
rebindiert einfach die Daten an gridView, indem die 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 Produkte einwandfrei, da diese Produkte Werte aufweisen ProductID
, die mit gültigen CategoryID
Werten ü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 und versucht, alle Produktwerte CategoryID
zu aktualisieren, führt jedoch zu einer Verletzung der Fremdschlüsseleinschränkung (siehe Abbildung 9).
Abbildung 8: Die Produkte werden in einer pageable GridView angezeigt (Zum Anzeigen des Bilds mit voller Größe klicken)
Abbildung 9: Erneutes Zuweisen der Kategorienergebnisse in einem Fremdschlüsseleinschränkungsverstoß (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 sollte die gleiche Ausgabe wie in Abbildung 8 dargestellt angezeigt werden. 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" (WITHOUT TRANSACTION) zu klicken. Dies führt zu demselben Fehler bei der Fremdschlüsseleinschränkung (siehe Abbildung 9), aber dieses Mal werden diese Produkte, deren CategoryID
Werte in einen rechtlichen Wert geändert wurden, nicht zurückgesetzt. 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
ersten acht Produkte neu zugewiesen. In Abbildung 8 hatte Chang beispielsweise eine CategoryID
Von 1, aber in Abbildung 10 wurde sie 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
Typ "Batchaktualisierung" erstellt, die das Batchaktualisierungsmuster verwendet, um die erforderlichen Änderungen an den Zeilen eines angegebenen ProductsDataTable
Typs auszuführen. Außerdem wurde die DeleteProductsWithTransaction
Methode der Klasse in der ProductsBLL
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 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 zugesichert.
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 nützliche 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 über mitchell@4GuysFromRolla.com seinen Blog erreicht werden, der unter .http://ScottOnWriting.NET
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 dies der Fall ist, legen Sie mir eine Zeile bei mitchell@4GuysFromRolla.com.