Freigeben über


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

von Scott Mitchell

PDF herunterladen

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

Einführung

Wie wir ab dem Tutorial Eine Übersicht über das Einfügen, Aktualisieren und Löschen von Daten gesehen haben, bietet GridView integrierte Unterstützung für das Bearbeiten und Löschen auf Zeilenebene. Mit wenigen Mausklicks ist es möglich, eine umfangreiche Datenänderungsschnittstelle zu erstellen, ohne eine Codezeile zu schreiben, solange Sie mit der Bearbeitung und Löschung pro Zeile zufrieden sind. In bestimmten Szenarien ist dies jedoch unzureichend, und wir müssen Benutzern die Möglichkeit geben, einen Batch von Datensätzen zu bearbeiten oder zu löschen.

Beispielsweise verwenden die meisten webbasierten E-Mail-Clients ein Raster, um jede Nachricht auflisten zu können, wobei jede Zeile ein Kontrollkästchen zusammen mit den E-Mail-Informationen (Betreff, Absender usw.) enthält. Diese Benutzeroberfläche ermöglicht es dem Benutzer, mehrere Nachrichten zu löschen, indem er sie überprüft und dann auf die Schaltfläche Ausgewählte Nachrichten löschen klickt. Eine Batchbearbeitungsschnittstelle eignet sich ideal in Situationen, in denen Benutzer häufig viele verschiedene Datensätze bearbeiten. Anstatt den Benutzer zu zwingen, auf Bearbeiten zu klicken, seine Änderung vorzunehmen und dann für jeden Datensatz, der geändert werden muss, auf Aktualisieren zu klicken, rendert eine Batchbearbeitungsoberfläche jede Zeile mit ihrer Bearbeitungsoberfläche. Der Benutzer kann den Satz von Zeilen, die geändert werden müssen, schnell ändern und diese Änderungen dann speichern, indem er auf die Schaltfläche Alle aktualisieren klickt. In diesen Tutorials wird untersucht, wie Sie Schnittstellen zum Einfügen, Bearbeiten und Löschen von Datenbatches erstellen.

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

Wenn der Batchvorgang als atomischer Vorgang behandelt werden soll, bei dem entweder alle Schritte erfolgreich sind oder alle Schritte fehlschlagen, muss die Datenzugriffsebene erweitert werden, um Die Unterstützung für Datenbanktransaktionen zu enthalten. Datenbanktransaktionen garantieren die Atomarität für den Satz von INSERT- , UPDATE- und DELETE -Anweisungen, die unter dem Dach der Transaktion ausgeführt werden, und sind ein Feature, das von den meisten modernen Datenbanksystemen unterstützt wird.

In diesem Tutorial erfahren Sie, wie Sie die DAL erweitern, um Datenbanktransaktionen zu verwenden. In den nachfolgenden Tutorials wird die Implementierung von Webseiten für das Einfügen, Aktualisieren und Löschen von Schnittstellen im Batch untersucht. Lassen Sie uns loslegen!

Hinweis

Beim Ändern von Daten in einer Batchtransaktion ist die Atomarität nicht immer erforderlich. In einigen Szenarien kann es akzeptabel sein, dass einige Datenänderungen erfolgreich sind und andere im selben Batch fehlschlagen, z. B. beim Löschen einer Reihe von E-Mails von einem webbasierten E-Mail-Client. Wenn während des Löschvorgangs ein Datenbankfehler auftritt, ist es wahrscheinlich akzeptabel, dass diese datensätze, die ohne Fehler verarbeitet werden, gelöscht bleiben. In solchen Fällen muss die DAL nicht geändert werden, um Datenbanktransaktionen zu unterstützen. Es gibt jedoch andere Batchbetriebsszenarien, in denen Atomarität von entscheidender Bedeutung ist. Wenn ein Kunde sein Geld von einem Bankkonto auf ein anderes verschiebt, müssen zwei Vorgänge durchgeführt werden: Das Guthaben muss vom ersten Konto abgezogen und dann dem zweiten Konto hinzugefügt werden. Während die Bank vielleicht nichts dagegen hat, dass der erste Schritt erfolgreich ist, aber der zweite Schritt fehlschlägt, wären ihre Kunden verständlicherweise verärgert. Ich ermutige Sie, dieses Tutorial durchzuarbeiten und die Verbesserungen an der DAL zu implementieren, um Datenbanktransaktionen zu unterstützen, auch wenn Sie nicht beabsichtigen, sie in den Folgenden drei Tutorials zum Einfügen, Aktualisieren und Löschen von Batchschnittstellen zu verwenden.

Übersicht über Transaktionen

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

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

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

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

Hinweis

Die TransactionScope -Klasse im System.Transactions -Namespace ermöglicht Entwicklern das programmgesteuerte Umschließen einer Reihe von Anweisungen innerhalb des Bereichs einer Transaktion und bietet Unterstützung für komplexe Transaktionen, die mehrere Quellen umfassen, z. B. zwei verschiedene Datenbanken oder sogar heterogene Typen von Datenspeichern, z. B. eine Microsoft SQL Server-Datenbank, eine Oracle-Datenbank und einen Webdienst. Ich habe mich entschieden, ADO.NET Transaktionen für dieses Tutorial anstelle der TransactionScope -Klasse zu verwenden, da ADO.NET für Datenbanktransaktionen spezifischer und in vielen Fällen viel weniger ressourcenintensiv ist. Darüber hinaus verwendet die TransactionScope Klasse in bestimmten Szenarien den Microsoft Distributed Transaction Coordinator (MSDTC). Die Konfigurations-, Implementierungs- und Leistungsprobleme im Zusammenhang mit MSDTC machen es zu einem eher spezialisierten und fortgeschrittenen Thema, das den Rahmen dieser Tutorials übersteigt.

Wenn Sie mit dem SqlClient-Anbieter in ADO.NET arbeiten, werden Transaktionen durch einen Aufruf der s-MethodeBeginTransaction der SqlConnection Klasse initiiert, die ein SqlTransaction -Objekt zurückgibt. Die Datenänderungsanweisungen, die die Transaktion bilden, werden innerhalb eines try...catch Blocks platziert. Wenn in einer Anweisung im Block ein Fehler auftritt, wird die try Ausführung an den catch Block übertragen, in dem die Transaktion über die Methode sRollback desSqlTransaction Objekts zurückgesetzt werden kann. Wenn alle Anweisungen erfolgreich abgeschlossen wurden, wird die Transaktion durch einen Aufruf der SqlTransaction Objekt-s-Methode Commit am Ende des try Blocks committet. Der folgende Codeausschnitt veranschaulicht dieses Muster. Weitere Informationen finden Sie unter Beibehalten 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 typisierten DataSet keine Transaktionen. Um Transaktionen zu unterstützen, müssen wir die TableAdapter-Klassen erweitern, um zusätzliche Methoden einzuschließen, die das obige Muster verwenden, um eine Reihe von Datenänderungsanweisungen innerhalb des Bereichs einer Transaktion auszuführen. In Schritt 2 erfahren Sie, wie Sie partielle Klassen verwenden, um diese Methoden hinzuzufügen.

Schritt 1: Erstellen der Webseiten zum Arbeiten mit Batchdaten

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

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

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

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

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

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

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

Fügen Sie schließlich diese vier Seiten als Einträge zur Web.sitemap Datei hinzu. Fügen Sie insbesondere nach dem Anpassen der Siteübersicht <siteMapNode>das folgende Markup hinzu:

<siteMapNode title="Working with Batched Data" 
    url="~/BatchData/Default.aspx" 
    description="Learn how to perform batch operations as opposed to 
                 per-row operations.">
    
    <siteMapNode title="Adding Support for Transactions" 
        url="~/BatchData/Transactions.aspx" 
        description="See how to extend the Data Access Layer to support 
                     database transactions." />
    <siteMapNode title="Batch Updating" 
        url="~/BatchData/BatchUpdate.aspx" 
        description="Build a batch updating interface, where each row in a 
                      GridView is editable." />
    <siteMapNode title="Batch Deleting" 
        url="~/BatchData/BatchDelete.aspx" 
        description="Explore how to create an interface for batch deleting 
                     by adding a CheckBox to each GridView row." />
    <siteMapNode title="Batch Inserting" 
        url="~/BatchData/BatchInsert.aspx" 
        description="Examine the steps needed to create a batch inserting 
                     interface, where multiple records can be created at the 
                     click of a button." />
</siteMapNode>

Nehmen Sie sich nach dem Aktualisieren Web.sitemapeinen Moment Zeit, um die Tutorials-Website über einen Browser anzuzeigen. Das Menü auf der linken Seite enthält nun Elemente für das Arbeiten mit Batchdatentutorials.

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

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

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

Wie bereits im ersten Tutorial beschrieben, erstellen einer Datenzugriffsebene, besteht das typisierte DataSet in unserer DAL aus DataTables und TableAdapters. Die DataTables enthalten Daten, während die TableAdapters die Funktionalität zum Lesen von Daten aus der Datenbank in die DataTables, zum Aktualisieren der Datenbank mit änderungen an den DataTables usw. bereitstellen. Denken Sie daran, dass die TableAdapters zwei Muster zum Aktualisieren von Daten bereitstellen, die ich als Batchupdate und DB-Direct bezeichnet habe. Mit dem Batchupdatemuster wird dem TableAdapter ein DataSet, eine DataTable oder eine Sammlung von DataRows übergeben. Diese Daten werden aufgezählt, und für jede eingefügte, geänderte oder gelöschte Zeile wird , InsertCommandUpdateCommandoder DeleteCommand ausgeführt. Beim DB-Direct-Muster wird dem TableAdapter stattdessen die Werte der Spalten übergeben, die zum Einfügen, Aktualisieren oder Löschen eines einzelnen Datensatzes erforderlich sind. Die DB Direct-Mustermethode verwendet dann diese übergebenen Werte, um die entsprechende InsertCommandAnweisung , UpdateCommandoder DeleteCommand auszuführen.

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

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

Das typisierte DataSet Northwind.xsd befindet sich im App_Code Ordner s-Unterordner DAL . Erstellen Sie einen Unterordner im DAL Ordner namens TransactionSupport , und fügen Sie eine neue Klassendatei mit dem Namen hinzu ProductsTableAdapter.TransactionSupport.cs (siehe Abbildung 4). Diese Datei enthält die partielle Implementierung von , die ProductsTableAdapter Methoden zum Durchführen von Datenänderungen mithilfe einer Transaktion enthält.

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

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

Die partial Schlüsselwort (keyword) in der Klassendeklaration gibt dem Compiler an, dass die darin hinzugefügten Member der ProductsTableAdapter Klasse im NorthwindTableAdapters Namespace hinzugefügt werden sollen. Notieren Sie sich die using System.Data.SqlClient Anweisung am Anfang der Datei. Da der TableAdapter für die Verwendung des SqlClient-Anbieters konfiguriert wurde, verwendet er intern ein SqlDataAdapter -Objekt, um seine Befehle an die Datenbank auszugeben. Daher müssen wir die SqlTransaction -Klasse verwenden, um die Transaktion zu beginnen und sie dann zu commiten oder ein Rollback durchzuführen. 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 markiert public, sodass sie von innerhalb von ProductsTableAdapter, aus einer anderen Klasse in der DAL oder von einer anderen Ebene in der Architektur, z. B. der BLL, verwendet werden können. BeginTransaction öffnet den internen SqlConnection TableAdapter-Wert (falls erforderlich), beginnt die Transaktion und weist sie der Transaction -Eigenschaft zu und fügt die Transaktion an die internen SqlDataAdapter s-Objekte SqlCommand an. CommitTransaction und RollbackTransaction rufen Sie die Transaction Objektmethoden s Commit bzw Rollback . die 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

Nach Abschluss dieser Methoden sind wir bereit, Methoden ProductsDataTable zu oder der BLL hinzuzufügen, die eine Reihe von Befehlen unter dem Dach einer Transaktion ausführen. Die folgende Methode verwendet das Batch Update-Muster, um eine ProductsDataTable instance mithilfe einer 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 AdapterUpdate Objekts zu einer Ausnahme führt, wird die Ausführung an den catch Block übertragen, in dem ein Rollback der Transaktion ausgeführt und die Ausnahme erneut ausgelöst wird. Denken Sie daran, dass die Update Methode das Batch Update-Muster implementiert, indem die Zeilen der angegebenen ProductsDataTable aufgelistet und die erforderlichen InsertCommand, UpdateCommandund DeleteCommand s ausgeführt werden. Wenn einer dieser Befehle zu einem Fehler führt, wird ein Rollback für die Transaktion ausgeführt, wodurch die vorherigen Änderungen rückgängig gemacht werden, die während der Lebensdauer der Transaktion vorgenommen wurden. Sollte die Update Anweisung ohne Fehler abgeschlossen werden, wird die Transaktion vollständig committet.

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.cshinzu. Alternativ kann diese Methode der Business Logic Layer-Klasse ProductsBLL mit einigen geringfügigen syntaktischen Änderungen hinzugefügt werden. Nämlich muss die Schlüsselwort (keyword) dies in this.BeginTransaction(), this.CommitTransaction()und this.RollbackTransaction() durch ersetzt Adapter werden (denken Sie daran, dass Adapter der Name einer Eigenschaft vom ProductsBLL Typ ProductsTableAdapterist).

Die UpdateWithTransaction Methode verwendet das Batch Update-Muster, aber eine Reihe von DB-Direct-Aufrufen kann auch innerhalb des Bereichs einer Transaktion verwendet werden, wie die folgende Methode zeigt. Die DeleteProductsWithTransaction -Methode akzeptiert als Eingabe eine List<T> vom Typ int, die die ProductID zu löschenden s sind. Die -Methode initiiert die Transaktion über einen Aufruf BeginTransaction von und durchläuft dann im try Block die angegebene Liste und ruft die DB-Direct Mustermethode Delete für jeden ProductID Wert auf. Wenn einer der Aufrufe von fehlschlägt Delete , wird die Steuerung an den catch Block übertragen, in dem ein Rollback für die Transaktion ausgeführt und die Ausnahme erneut ausgelöst wird. Wenn alle Aufrufe erfolgreich sind Delete , wird die Transaktion zu einem Commit verpflichtet. 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 Tutorial untersuchte transaktionsbezogene Code ermöglicht es, dass mehrere Anweisungen für den ProductsTableAdapter als atomarer Vorgang behandelt werden können. Was aber, wenn mehrere Änderungen an verschiedenen Datenbanktabellen atomar durchgeführt werden müssen? Für instance sollten wir beim Löschen einer Kategorie zunächst die aktuellen Produkte einer anderen Kategorie zuweisen. Diese beiden Schritte zum Erneuten Zuweisen der Produkte und löschen der Kategorie sollten als atomarer Vorgang ausgeführt werden. ProductsTableAdapter Der enthält jedoch nur Methoden zum Ändern der Products Tabelle und enthält CategoriesTableAdapter nur Methoden zum Ändern der Categories Tabelle. Wie kann eine Transaktion also beide TableAdapter umfassen?

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

Eine weitere Option besteht darin, eine Hilfsklasse in der DAL zu erstellen, die die DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID) -Methode enthält. Diese Methode erstellt eine instance von CategoriesTableAdapter und und ProductsTableAdapter und legt dann diese beiden TableAdapters-Eigenschaften Connection auf dieselbe SqlConnection instance fest. Zu diesem Zeitpunkt würde einer der beiden TableAdapters die Transaktion mit einem Aufruf von BeginTransactioninitiieren. Die TableAdapters-Methoden zum erneuten Zuweisen der Produkte und zum Löschen der Kategorie werden in einem try...catch Block mit einem Commit für die Transaktion aufgerufen oder bei Bedarf ein Rollback ausgeführt.

Schritt 4: Hinzufügen derUpdateWithTransactionMethode zur Geschäftslogikebene

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

Öffnen Sie die ProductsBLL Klassendatei, und fügen Sie eine Methode mit dem Namen hinzu UpdateWithTransaction , die einfach die entsprechende DAL-Methode aufruft. Es sollten nun zwei neue Methoden in ProductsBLLvorhanden sein: UpdateWithTransaction, die Sie gerade hinzugefügt haben, und DeleteProductsWithTransaction, 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 den DataObjectMethodAttribute 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, DataObjectMethodAttribute um zu kennzeichnen, welche Methoden im ObjectDataSource-Assistenten zum Konfigurieren von Datenquellen und auf welcher Registerkarte (SELECT, UPDATE, INSERT oder DELETE) angezeigt werden sollen. Da gridView keine integrierte Unterstützung für die Batchbearbeitung oder -löschung bietet, müssen wir diese Methoden programmgesteuert aufrufen, anstatt den codefreien deklarativen Ansatz zu verwenden.

Schritt 5: Atomares Aktualisieren von Datenbankdaten von der Präsentationsebene

Um den Effekt zu veranschaulichen, den die Transaktion beim Aktualisieren eines Batches von Datensätzen hat, erstellen Sie eine Benutzeroberfläche, die alle Produkte in einer GridView listet und ein Button-Websteuerelement enthält, das beim Klicken die Produktwerte CategoryID neu zuweisen kann. Insbesondere wird die Neuzuweisung der Kategorie fortgesetzt, sodass den ersten mehreren Produkten ein gültiger CategoryID Wert zugewiesen wird, während anderen absichtlich ein nicht vorhandener CategoryID Wert zugewiesen wird. Wenn wir versuchen, die Datenbank mit einem Produkt zu aktualisieren, das CategoryID nicht mit einer vorhandenen Kategorie übereinstimmt, CategoryIDtritt eine Verletzung der Fremdschlüsseleinschränkung auf, und es wird eine Ausnahme ausgelöst. In diesem Beispiel sehen wir, dass bei Verwendung einer Transaktion die ausnahme, die von der Verletzung der Fremdschlüsseleinschränkung ausgelöst wird, dazu führt, dass die vorherigen gültigen CategoryID Änderungen zurückgesetzt werden. Wenn sie keine Transaktion verwenden, bleiben die Änderungen an den ursprünglichen Kategorien jedoch bestehen.

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

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

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

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

Abbildung 6: Legen Sie die Drop-Down Listen in den Registerkarten UPDATE, INSERT und DELETE auf (Keine) fest (Klicken Sie, um das bild in voller Größe anzuzeigen)

Nach Abschluss des Assistenten "Datenquelle konfigurieren" erstellt Visual Studio BoundFields und ein CheckBoxField-Element für die Produktdatenfelder. Entfernen Sie alle diese Felder mit Ausnahme ProductIDvon , ProductName, CategoryIDund CategoryName benennen Sie die ProductName Eigenschaften und CategoryName BoundFields HeaderText in Product bzw. Category um. Aktivieren Sie im Smarttag die Option Paging aktivieren. Nach diesen Änderungen sollte das deklarative Markup 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 über der GridView hinzu. Legen Sie die erste Texteigenschaft der Schaltfläche auf Raster aktualisieren fest, die zweite auf Kategorien ändern (WITH TRANSACTION) und die dritte auf Kategorien ändern (OHNE TRANSAKTION).

<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 Screenshot in Abbildung 7 ähneln.

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

Abbildung 7: Die Seite enthält ein GridView- und drei Schaltflächen-Websteuerelement (Klicken Sie, 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 Aktualisierungs-Ereignishandler von Button wird Click die Daten einfach durch Aufrufen der GridView-Methode DataBind neu an die Products GridView-Methode gebunden.

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

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

Um dieses Verhalten zu veranschaulichen, besuchen Sie diese Seite über einen Browser. Zunächst sollte die erste Seite der Daten angezeigt werden, wie in Abbildung 8 dargestellt. Klicken Sie als Nächstes auf die Schaltfläche Kategorien ändern (WITH TRANSACTION). Dies führt zu einem Postback und versucht, alle Produktwerte CategoryID zu aktualisieren, führt jedoch zu einer Verletzung der Fremdschlüsseleinschränkung (siehe Abbildung 9).

Die Produkte werden in einer pageable GridView angezeigt.

Abbildung 8: Die Produkte werden in einer Auslagerungsrasteransicht angezeigt (Klicken Sie hier, um das bild in voller Größe anzuzeigen)

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

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

Klicken Sie nun auf die Schaltfläche Zurück in Ihrem Browser, und klicken Sie dann auf die Schaltfläche Raster aktualisieren. Beim Aktualisieren der Daten sollte genau dieselbe Ausgabe wie in Abbildung 8 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, als die Fremdschlüsseleinschränkung verletzt wurde.

Versuchen Sie nun, auf die Schaltfläche Kategorien ändern (OHNE TRANSAKTION) zu klicken. Dies führt zu demselben Fehler bei der Verletzung von Fremdschlüsseleinschränkungen (siehe Abbildung 9), aber dieses Mal werden die Produkte, deren Werte in einen legalen CategoryID Wert geändert wurden, nicht zurückgesetzt. Klicken Sie in Ihrem Browser auf die Schaltfläche "Zurück" und dann auf die Schaltfläche "Raster aktualisieren". Wie Abbildung 10 zeigt, wurden die CategoryID s der ersten acht Produkte neu zugewiesen. In Abbildung 8 hatte Chang z. B. einen CategoryID von 1, aber in Abbildung 10 wurde es zu 2 neu zugewiesen.

Bei einigen Produkten wurden CategoryID-Werte aktualisiert, während andere nicht waren.

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

Zusammenfassung

Standardmäßig umschließen die TableAdapter-Methoden die ausgeführten Datenbankanweisungen nicht innerhalb des Bereichs einer Transaktion, aber mit wenig Aufwand können wir Methoden hinzufügen, die eine Transaktion erstellen, committen und rollbacken. In diesem Tutorial haben wir drei solche Methoden in der ProductsTableAdapter -Klasse erstellt: BeginTransaction, CommitTransactionund RollbackTransaction. Wir haben gesehen, wie diese Methoden zusammen mit einem try...catch -Block verwendet werden, um eine Reihe von Datenänderungsanweisungen atomar zu machen. Insbesondere haben wir die UpdateWithTransaction -Methode in erstellt ProductsTableAdapter, die das Batchupdatemuster verwendet, um die erforderlichen Änderungen an den Zeilen einer angegebenen ProductsDataTabledurchzuführen. Außerdem haben wir die DeleteProductsWithTransaction -Methode der ProductsBLL -Klasse in der BLL hinzugefügt, die eine List von ProductID -Werten als Eingabe akzeptiert und die DB-Direct-Mustermethode Delete für jede ProductIDaufruft. Beide Methoden beginnen damit, eine Transaktion zu erstellen und dann die Datenänderungsanweisungen in einem try...catch -Block auszuführen. Wenn eine Ausnahme auftritt, wird für die Transaktion ein Rollback ausgeführt, andernfalls wird ein Commit ausgeführt.

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

Viel Spaß beim Programmieren!

Weitere Informationen

Weitere Informationen zu den in diesem Tutorial behandelten Themen finden Sie in den folgenden Ressourcen:

Zum Autor

Scott Mitchell, Autor von sieben ASP/ASP.NET-Büchern und Gründer von 4GuysFromRolla.com, arbeitet seit 1998 mit Microsoft-Webtechnologien. Scott arbeitet als unabhängiger Berater, Trainer und Autor. Sein neuestes Buch ist Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Er kann unter mitchell@4GuysFromRolla.comoder über seinen Blog erreicht werden, der unter http://ScottOnWriting.NETzu finden ist.

Besonderer Dank an

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