Freigeben über


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

von Scott Mitchell

PDF herunterladen

Dieses Lernprogramm ist das erste von vier, das sich mit dem Aktualisieren, Löschen und Einfügen von Datenstapeln 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 in dem Tutorial „Ein Überblick über das Einfügen, Aktualisieren und Löschen von Daten“ gesehen haben, bietet der 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 einzige Codezeile zu schreiben, solange Sie damit zufrieden sind, Bearbeitungen und Löschungen zeilenweise vorzunehmen. 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 Änderungen vorzunehmen und danach für jeden Datensatz, der geändert werden muss, auf "Aktualisieren" zu klicken, ermöglicht eine Batch-Bearbeitungsschnittstelle das Bearbeiten jeder Zeile direkt in 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, UPDATEund DELETE Anweisungen, die unter dem Dach der Transaktion ausgeführt werden und sind ein Feature, das von den meisten modernen Datenbanksystemen unterstützt wird.

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

Hinweis

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

Übersicht über Transaktionen

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

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

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

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 Tutorial werden wir uns mit der Verwendung gespeicherter Prozeduren in der Datenzugriffsebene befassen, wobei wir die SQL-Anweisungen zum Erstellen, Committen und Zurücksetzen 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 durch einen Aufruf der Methode der KlasseSqlConnection initiiert, die ein SqlTransaction zurückgibt. Die Datenänderungsanweisungen, die die Transaktion bilden, werden in einem try...catch Block platziert. Wenn in einer Anweisung im try Block ein Fehler auftritt, wird die Ausführung an den catch Block übertragen, in dem die Transaktion über die Methode des SqlTransaction Objekts Rollbackzurückgesetzt werden kann. Wenn alle Anweisungen erfolgreich abgeschlossen wurden, wird die Transaktion durch einen Aufruf der Methode Commit des SqlTransaction Objekts am Ende des try Blocks ausgeführt und bestätigt. 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 von Webseiten für den Umgang mit Batchdaten

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

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

Hinzufügen der ASP.NET Seiten für sqlDataSource-bezogene Lernprogramme

Abbildung 1: Hinzufügen der ASP.NET Seiten für sqlDataSource-bezogene Lernprogramme

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

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

Abbildung 2: Hinzufügen des SectionLevelTutorialListing.ascx Benutzersteuerelements zu 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 anzuzeigen. Das Menü auf der linken Seite enthält jetzt Elemente für Tutorials zum Arbeiten mit Batchdaten.

Die Sitemap enthält jetzt Einträge für die Tutorials zur Arbeit mit gebatchten Daten

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

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

Wie wir im ersten Lernprogramm, Erstellung einer Datenzugriffsschicht, erläutert 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 Batch-Update-Muster wird der TableAdapter an ein DataSet, DataTable 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, UpdateCommandoder DeleteCommand wird ausgeführt. Mit dem DB-Direct-Muster wird der TableAdapter stattdessen die Werte der Spalten übergeben, die zum Einfügen, Aktualisieren oder Löschen eines einzelnen Datensatzes erforderlich sind. Die DB Direct-Mustermethode verwendet dann die übergebenen Werte, um die entsprechende InsertCommand, UpdateCommandoder DeleteCommand Anweisung auszuführen.

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

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 InsertCommand, UpdateCommand und DeleteCommand unter dem Dach einer Transaktion ausführen. 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 des ProductsTableAdapter, die Methoden zur Durchführung von Datenänderungen mithilfe einer Transaktion einschließt.

Hinzufügen eines Ordners mit dem Namen

Abbildung 4: Hinzufügen eines Ordners namens TransactionSupport und einer Klassendatei mit dem Namen ProductsTableAdapter.TransactionSupport.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 Klassendeklaration hier zeigt dem Compiler an, dass 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 bestätigen 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 mit public gekennzeichnet, sodass sie innerhalb der ProductsTableAdapter, einer anderen Klasse im DAL oder einer anderen Ebene in der Architektur, wie z. B. 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 des Transaction Objekts Commit beziehungsweise Rollback auf, 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

Nachdem diese Methoden fertiggestellt sind, sind wir bereit, Methoden zur ProductsDataTable oder zur BLL hinzuzufügen, die eine Reihe von Befehlen im Rahmen 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 sowie die erforderlichen InsertCommand, UpdateCommand und DeleteCommands 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 durchgeführt.

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 Partial-Klasse in ProductsTableAdapter.TransactionSupport.cs hinzu. Alternativ kann diese Methode der Business Logic Layer-Klasse ProductsBLL mit einigen geringfügigen syntaktischen Änderungen hinzugefügt werden. Das Schlüsselwort "this" in this.BeginTransaction(), this.CommitTransaction() und this.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 Objekt des Typs int, welches die zu löschenden ProductIDs darstellt. Die Methode initiiert die Transaktion über einen Aufruf von BeginTransaction und iteriert dann in dem try Block durch die angegebene Liste, wobei sie die Methode des DB-Direct-Musters Delete für jeden ProductID Wert aufruft. Wenn einer der Aufrufe Delete fehlschlägt, wird die Steuerung an den Block catch übergeben, wo die Transaktion zurückgesetzt und die Ausnahme erneut ausgelöst wird. Wenn alle Aufrufe Delete erfolgreich ausgeführt werden, wird die Transaktion bestätigt. 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, dass mehrere Anweisungen gegenüber ProductsTableAdapter als Atomoperation behandelt werden. 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 würde eine Instanz von CategoriesTableAdapter und von ProductsTableAdapter erstellen und dann die TableAdapter-Eigenschaften Connection auf dieselbe SqlConnection-Instanz setzen. An diesem Punkt würde einer der beiden TableAdapters die Transaktion mit einem Aufruf von 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 nach Bedarf entweder bestätigt oder zurückgesetzt wird.

Schritt 4: Hinzufügen derUpdateWithTransactionMethode zur Geschäftslogikebene

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

Öffnen Sie die ProductsBLL Klassendatei und fügen Sie eine Methode UpdateWithTransaction hinzu, welche einfach die entsprechende DAL-Methode aufruft. Nun sollten zwei neue Methoden in ProductsBLL vorhanden sein: 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 schließen das Attribut, das DataObjectMethodAttribute den meisten anderen Methoden in der ProductsBLL-Klasse zugewiesen ist, nicht ein, da wir diese Methoden direkt aus den Code-behind-Klassen der ASP.NET-Seiten aufrufen werden. Denken Sie daran, dass DataObjectMethodAttribute verwendet wird, um zu kennzeichnen, welche Methoden im Konfigurationsassistenten für Datenquellen von ObjectDataSource und auf 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 eine Button-Websteuerung enthält, die beim Klicken die CategoryID-Werte der Produkte 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 für das ID den Wert Products fest und binden Sie es über das Smarttag an eine neue ObjectDataSource mit dem Namen ProductsDataSource. Konfigurieren Sie objectDataSource, um die Daten aus der ProductsBLL Klassenmethode GetProducts abzurufen. Dies wird eine schreibgeschützte GridView sein. Legen Sie daher die Dropdown-Listen in den Registerkarten UPDATE, EINFÜGEN und LÖSCHEN auf (Keine) fest und klicken Sie auf "Fertig stellen".

Abbildung 5: Konfigurieren der ObjectDataSource zur Verwendung der GetProducts-Methode der ProductsBLL-Klasse

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

Legen Sie die Dropdownlisten in den Registerkarten UPDATE, INSERT und DELETE auf (Keine) fest.

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 ProductID, ProductName, CategoryID und CategoryName und benennen Sie die Eigenschaften der ProductName- und CategoryName-BoundFields in Produkt und Kategorie um. Aktivieren Sie vom Smart-Tag 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 "Grid aktualisieren", der zweiten auf "Kategorien ändern (MIT TRANSAKTION)" und der dritten auf "Kategorien ändern (OHNE TRANSAKTION)" fest.

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

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

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

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

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

protected 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 des Refresh-Buttons Click bindet die Daten einfach erneut an die GridView, indem die Methode von GridView DataBind aufgerufen wird.

Der zweite Ereignishandler weist die Produkte CategoryID neu zu und verwendet die neue Transaktionsmethode aus der BLL, um die Datenbankaktualisierungen unter dem Dach einer Transaktion auszuführen. Beachten Sie, dass jedes Produkt CategoryID beliebig auf denselben Wert wie das produkt ProductIDfestgelegt ist. Dies funktioniert für die ersten wenigen Produkte einwandfrei, da diese Produkte ProductID Werte aufweisen, die mit gültigen CategoryID Werten übereinstimmen. Sobald die ProductID s zunehmend zu groß werden, 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" (MIT TRANSAKTION). 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).

Die Produkte werden in einer seitenumblätterbaren GridView angezeigt.

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

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

Abbildung 9: Das erneute Zuweisen 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 genau dieselbe Ausgabe sehen, wie in Abbildung 8 dargestellt. 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 der Verletzung der Fremdschlüsseleinschränkung (siehe Abbildung 9), aber dieses Mal werden diese Produkte, deren CategoryID Werte auf 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 es auf 2 neuzugewiesen.

Die CategoryID-Werte einiger Produkte wurden aktualisiert, während andere nicht aktualisiert wurden.

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

Zusammenfassung

Standardmäßig schließen die TableAdapter-Methoden die ausgeführten Datenbankanweisungen nicht innerhalb des Bereichs einer Transaktion um, aber mit etwas Arbeit können wir Methoden hinzufügen, die eine Transaktion erstellen, übernehmen und zurücksetzen. In diesem Lernprogramm haben wir drei solche Methoden in der ProductsTableAdapter Klasse erstellt: BeginTransaction, , CommitTransactionund RollbackTransaction. Wir haben gesehen, wie diese Methoden zusammen mit einem try...catch Block verwendet werden, um eine Reihe von Datenänderungsanweisungen atomar zu machen. Insbesondere haben wir die Methode UpdateWithTransaction innerhalb von ProductsTableAdapter erstellt, die das Batch-Update-Muster verwendet, um die erforderlichen Änderungen an den Zeilen eines angegebenen ProductsDataTable auszuführen. Außerdem haben wir die DeleteProductsWithTransaction-Methode zur ProductsBLL-Klasse in der BLL hinzugefügt, die eine List von ProductID-Werten als Eingabe akzeptiert und für jedes ProductID die DB-Direct-Pattern-Methode Delete 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 festgeschrieben.

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 Lektüre

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

Zum Autor

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

Besonderer Dank an

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