Udostępnij za pośrednictwem


Opakowywanie modyfikacji bazy danych w ramach transakcji (C#)

Autor: Scott Mitchell

Pobierz plik PDF

Ten samouczek to pierwszy z czterech, który analizuje aktualizowanie, usuwanie i wstawianie partii danych. W tym samouczku dowiesz się, jak transakcje bazy danych umożliwiają wykonywanie modyfikacji wsadowych jako niepodzielnej operacji, co gwarantuje, że wszystkie kroki kończą się powodzeniem lub wszystkie kroki kończą się niepowodzeniem.

Wprowadzenie

Jak widzieliśmy, począwszy od samouczka Omówienie wstawiania, aktualizowania i usuwania danych , funkcja GridView zapewnia wbudowaną obsługę edytowania i usuwania na poziomie wiersza. Za pomocą kilku kliknięć myszy można utworzyć rozbudowany interfejs modyfikacji danych bez konieczności pisania wiersza kodu, tak długo, jak jesteś zawartością edycji i usuwania dla poszczególnych wierszy. Jednak w niektórych scenariuszach jest to niewystarczające i musimy zapewnić użytkownikom możliwość edytowania lub usuwania partii rekordów.

Na przykład większość internetowych klientów poczty e-mail używa siatki do wyświetlania listy poszczególnych wiadomości, w których każdy wiersz zawiera pole wyboru wraz z informacjami e-mail (temat, nadawca itd.). Ten interfejs umożliwia użytkownikowi usunięcie wielu komunikatów, sprawdzając je, a następnie klikając przycisk Usuń wybrane komunikaty. Interfejs edycji wsadowej jest idealny w sytuacjach, w których użytkownicy często edytują wiele różnych rekordów. Zamiast wymuszać na użytkowniku kliknięcie pozycji Edytuj, wprowadź zmianę, a następnie kliknij przycisk Aktualizuj dla każdego rekordu, który musi zostać zmodyfikowany, interfejs edycji wsadowej renderuje każdy wiersz za pomocą interfejsu edycji. Użytkownik może szybko zmodyfikować zestaw wierszy, które należy zmienić, a następnie zapisać te zmiany, klikając przycisk Aktualizuj wszystko. W tym zestawie samouczków dowiesz się, jak tworzyć interfejsy do wstawiania, edytowania i usuwania partii danych.

Podczas wykonywania operacji wsadowych ważne jest ustalenie, czy niektóre operacje w partii powinny być możliwe, gdy inne kończą się niepowodzeniem. Rozważ usunięcie wsadowego interfejsu — co powinno się zdarzyć, jeśli pierwszy wybrany rekord zostanie pomyślnie usunięty, ale drugi kończy się niepowodzeniem, powiedzmy, z powodu naruszenia ograniczenia klucza obcego? Czy pierwsze usunięcie rekordu powinno zostać wycofane lub jest dopuszczalne, aby pierwszy rekord pozostał usunięty?

Jeśli chcesz, aby operacja wsadowa była traktowana jako operacja niepodzielna, w której wszystkie kroki zakończyły się powodzeniem lub wszystkie kroki zakończyły się niepowodzeniem, należy rozszerzyć warstwę dostępu do danych, aby uwzględnić obsługę transakcji bazy danych. Transakcje bazy danych gwarantują niepodzielność zestawu INSERTinstrukcji , UPDATEi DELETE wykonywanych pod parasolem transakcji i są funkcją obsługiwaną przez większość nowoczesnych systemów baz danych.

W tym samouczku dowiesz się, jak rozszerzyć zakres dal na użycie transakcji bazy danych. Kolejne samouczki będą badać implementację stron internetowych na potrzeby wstawiania, aktualizowania i usuwania interfejsów wsadowych. Zacznijmy!

Uwaga

Podczas modyfikowania danych w transakcji wsadowej nie zawsze jest potrzebna niepodzielność. W niektórych scenariuszach dopuszczalne może być pomyślne wprowadzenie pewnych modyfikacji danych, a inne w tej samej partii kończą się niepowodzeniem, na przykład podczas usuwania zestawu wiadomości e-mail z internetowego klienta poczty e-mail. Jeśli w trakcie procesu usuwania występuje błąd bazy danych, prawdopodobnie dopuszczalne jest, że te rekordy przetwarzane bez błędu pozostaną usunięte. W takich przypadkach dal nie musi być modyfikowana w celu obsługi transakcji bazy danych. Istnieją jednak inne scenariusze operacji wsadowych, w których niepodzielność jest niezbędna. Gdy klient przenosi swoje fundusze z jednego konta bankowego do drugiego, należy wykonać dwie operacje: środki muszą zostać odliczone od pierwszego konta, a następnie dodane do drugiego. Chociaż bank może nie mieć nic przeciwko pierwszemu sukcesowi, ale drugi krok nie powiedzie się, jego klienci będą zrozumiałe zdenerwowani. Zachęcam cię do pracy z tym samouczkiem i zaimplementowania ulepszeń do obsługi transakcji bazy danych, nawet jeśli nie planujesz ich używać w wsadowych wstawiania, aktualizowania i usuwania interfejsów, które utworzymy w trzech poniższych samouczkach.

Omówienie transakcji

Większość baz danych obejmuje obsługę transakcji, co umożliwia grupowanie wielu poleceń bazy danych w jedną jednostkę logiczną pracy. Polecenia bazy danych składające się z transakcji są gwarantowane niepodzielne, co oznacza, że wszystkie polecenia zakończy się niepowodzeniem lub wszystkie zostaną wykonane pomyślnie.

Ogólnie rzecz biorąc, transakcje są implementowane za pomocą instrukcji SQL przy użyciu następującego wzorca:

  1. Wskazuje początek transakcji.
  2. Wykonaj instrukcje SQL, które składają się na transakcję.
  3. Jeśli wystąpi błąd w jednej z instrukcji z kroku 2, wycofaj transakcję.
  4. Jeśli wszystkie instrukcje z kroku 2 zakończą się bez błędu, zatwierdź transakcję.

Instrukcje SQL używane do tworzenia, zatwierdzania i wycofywania transakcji można wprowadzić ręcznie podczas pisania skryptów SQL lub tworzenia procedur składowanych albo za pomocą środków programistycznych przy użyciu ADO.NET lub klas w System.Transactions przestrzeni nazw. W tym samouczku przeanalizujemy tylko zarządzanie transakcjami przy użyciu ADO.NET. W przyszłym samouczku przyjrzymy się, jak używać procedur składowanych w warstwie dostępu do danych, w tym czasie zapoznamy się z instrukcjami SQL dotyczącymi tworzenia, wycofywania i zatwierdzania transakcji.

Uwaga

Klasa TransactionScope w System.Transactions przestrzeni nazw umożliwia deweloperom programowe zawijanie serii instrukcji w zakresie transakcji i obejmuje obsługę złożonych transakcji obejmujących wiele źródeł, takich jak dwie różne bazy danych, a nawet heterogeniczne typy magazynów danych, takie jak baza danych programu Microsoft SQL Server, baza danych Oracle i usługa sieci Web. Postanowiłem użyć transakcji ADO.NET na potrzeby tego samouczka zamiast TransactionScope klasy, ponieważ ADO.NET jest bardziej specyficzna dla transakcji bazy danych, a w wielu przypadkach jest znacznie mniej intensywnie obciążana zasobami. Ponadto w niektórych scenariuszach TransactionScope klasa używa koordynatora transakcji rozproszonych firmy Microsoft (MSDTC). Problemy z konfiguracją, implementacją i wydajnością związane z MSDTC sprawiają, że jest to dość wyspecjalizowany i zaawansowany temat oraz poza zakresem tych samouczków.

Podczas pracy z dostawcą SqlClient w ADO.NET transakcje są inicjowane za pośrednictwem wywołania SqlConnection metody klasy sBeginTransaction, która zwraca SqlTransaction obiekt. Instrukcje modyfikacji danych, które makijaż transakcji są umieszczane w try...catch bloku. Jeśli w instrukcji w try bloku wystąpi błąd, wykonywanie jest transferowane do catch bloku, w którym transakcja może zostać wycofana za pośrednictwem SqlTransaction metody obiektu sRollback. Jeśli wszystkie instrukcje zakończą się pomyślnie, wywołanie SqlTransaction metody object s Commit na końcu try bloku zatwierdza transakcję. Poniższy fragment kodu ilustruje ten wzorzec. Zobacz Obsługa spójności bazy danych za pomocą transakcji.

// 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;
}

Domyślnie klasy TableAdapters w typowanym zestawie danych nie używają transakcji. Aby zapewnić obsługę transakcji, musimy rozszerzyć klasy TableAdapter w celu uwzględnienia dodatkowych metod, które używają powyższego wzorca do wykonywania serii instrukcji modyfikacji danych w zakresie transakcji. W kroku 2 zobaczymy, jak używać klas częściowych do dodawania tych metod.

Krok 1. Tworzenie pracy ze stronami sieci Web danych wsadowych

Zanim zaczniemy eksplorować sposób rozszerzania dal w celu obsługi transakcji bazy danych, najpierw pośmińmy chwilę na utworzenie ASP.NET stron internetowych, których będziemy potrzebować w tym samouczku i trzech poniższych. Zacznij od dodania nowego folderu o nazwie BatchData , a następnie dodaj następujące strony ASP.NET, kojarząc każdą stronę ze stroną wzorcową Site.master .

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

Dodawanie stron ASP.NET dla samouczków związanych z usługą SqlDataSource

Rysunek 1. Dodawanie stron ASP.NET dla samouczków związanych z usługą SqlDataSource

Podobnie jak w przypadku innych folderów, użyje kontrolki SectionLevelTutorialListing.ascx użytkownika, Default.aspx aby wyświetlić listę samouczków w jej sekcji. W związku z tym dodaj tę kontrolkę Default.aspx użytkownika, przeciągając ją z Eksplorator rozwiązań na stronę Widok projektu.

Dodaj kontrolkę Użytkownika SectionLevelTutorialListing.ascx, aby Default.aspx

Rysunek 2. Dodawanie kontrolki SectionLevelTutorialListing.ascx użytkownika do Default.aspx (kliknij, aby wyświetlić obraz o pełnym rozmiarze)

Na koniec dodaj te cztery strony jako wpisy do Web.sitemap pliku. W szczególności dodaj następujący znacznik po dostosowaniu mapy <siteMapNode>witryny :

<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>

Po zaktualizowaniu Web.sitemapprogramu pośmiń chwilę, aby wyświetlić witrynę internetową samouczków za pośrednictwem przeglądarki. Menu po lewej stronie zawiera teraz elementy do pracy z samouczkami dotyczącymi danych wsadowych.

Mapa witryny zawiera teraz wpisy dotyczące pracy z danymi wsadowymi

Rysunek 3. Mapa witryny zawiera teraz wpisy dotyczące pracy z samouczkami dotyczącymi danych wsadowych

Krok 2. Aktualizowanie warstwy dostępu do danych w celu obsługi transakcji bazy danych

Jak już omówiliśmy w pierwszym samouczku, tworzenie warstwy dostępu do danych, typizowany zestaw danych w usłudze DAL składa się z tabel DataTables i TableAdapters. Tabele DataTable przechowują dane, podczas gdy elementy TableAdapters zapewniają funkcjonalność odczytywania danych z bazy danych do tabel DataTables, aktualizowania bazy danych o zmiany wprowadzone w tabelach DataTable i tak dalej. Pamiętaj, że klasy TableAdapters udostępniają dwa wzorce aktualizacji danych, które są określane jako Batch Update i DB-Direct. W przypadku wzorca aktualizacji usługi Batch element TableAdapter jest przekazywany do zestawu danych, tabeli DataTable lub kolekcji DataRows. Te dane są wyliczane i dla każdego wstawionego, zmodyfikowanego lub usuniętego InsertCommandwiersza, wykonywane jest polecenie , UpdateCommandlub DeleteCommand . W przypadku wzorca DB-Direct funkcja TableAdapter jest zamiast tego przekazywana wartości kolumn niezbędnych do wstawiania, aktualizowania lub usuwania pojedynczego rekordu. Metoda wzorca direct bazy danych używa następnie tych przekazanych wartości do wykonania odpowiedniej InsertCommandinstrukcji , UpdateCommand, lub DeleteCommand .

Niezależnie od używanego wzorca aktualizacji metody generowane automatycznie nie korzystają z transakcji. Domyślnie każda operacja wstawiania, aktualizowania lub usuwania wykonywana przez obiekt TableAdapter jest traktowana jako pojedyncza dyskretna operacja. Załóżmy na przykład, że wzorzec DB-Direct jest używany przez jakiś kod w usłudze BLL do wstawiania dziesięciu rekordów do bazy danych. Ten kod wywoła metodę Insert TableAdapter dziesięć razy. Jeśli pierwsze pięć wstawień powiedzie się, ale szósty spowodował wyjątek, pierwsze pięć wstawionych rekordów pozostanie w bazie danych. Podobnie, jeśli wzorzec aktualizacji usługi Batch jest używany do wykonywania operacji wstawiania, aktualizacji i usuwania do wstawionych, zmodyfikowanych i usuniętych wierszy w tabeli DataTable, jeśli pierwsze kilka modyfikacji zakończyło się pomyślnie, ale później napotkał błąd, te wcześniejsze modyfikacje, które zostały ukończone, pozostaną w bazie danych.

W niektórych scenariuszach chcemy zapewnić niepodzielność w ramach serii modyfikacji. Aby to osiągnąć, musimy ręcznie rozszerzyć tabelę TableAdapter, dodając nowe metody, które wykonują InsertCommandmetodę , UpdateCommandi DeleteCommand s pod parasolem transakcji. W obszarze Tworzenie warstwy dostępu do danych przyjrzeliśmy się używaniu klas częściowych w celu rozszerzenia funkcjonalności tabel Danych w ramach typowego zestawu danych. Ta technika może być również używana z elementami TableAdapters.

Typowany zestaw Northwind.xsd danych znajduje się w App_Code podfolderze folderu s DAL . Utwórz podfolder w folderze DAL o nazwie i dodaj nowy plik klasy o nazwie ProductsTableAdapter.TransactionSupport.cs TransactionSupport (zobacz Rysunek 4). Ten plik będzie przechowywać częściową implementację ProductsTableAdapter , która zawiera metody przeprowadzania modyfikacji danych przy użyciu transakcji.

Dodawanie folderu o nazwie TransactionSupport i pliku klasy o nazwie ProductsTableAdapter.TransactionSupport.cs

Rysunek 4. Dodawanie folderu o nazwie i pliku klasy o nazwie TransactionSupportProductsTableAdapter.TransactionSupport.cs

Wprowadź następujący kod w ProductsTableAdapter.TransactionSupport.cs pliku:

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();
        }
   }
}

Słowo partial kluczowe w deklaracji klasy wskazuje kompilatorowi, że składowe dodane w ramach programu mają zostać dodane do ProductsTableAdapter klasy w NorthwindTableAdapters przestrzeni nazw. Zanotuj instrukcję using System.Data.SqlClient w górnej części pliku. Ponieważ obiekt TableAdapter został skonfigurowany do używania dostawcy SqlClient, wewnętrznie używa SqlDataAdapter obiektu do wydawania poleceń do bazy danych. W związku z tym musimy użyć SqlTransaction klasy , aby rozpocząć transakcję, a następnie zatwierdzić ją lub wycofać. Jeśli używasz magazynu danych innego niż program Microsoft SQL Server, musisz użyć odpowiedniego dostawcy.

Te metody zapewniają bloki konstrukcyjne potrzebne do uruchomienia, wycofania i zatwierdzenia transakcji. Są one oznaczone jako public, co umożliwia ich korzystanie z poziomu ProductsTableAdapterklasy , z innej klasy w dal lub z innej warstwy w architekturze, takiej jak BLL. BeginTransaction Otwiera wewnętrzny SqlConnection obiekt TableAdapter (w razie potrzeby), rozpoczyna transakcję i przypisuje ją do Transaction właściwości oraz dołącza transakcję do wewnętrznych SqlDataAdapter obiektów s SqlCommand . CommitTransactioni RollbackTransaction wywołaj Transaction odpowiednio metody i Rollback obiektów Commit przed zamknięciem obiektu wewnętrznegoConnection.

Krok 3. Dodawanie metod do aktualizowania i usuwania danych w ramach parasola transakcji

Po zakończeniu tych metod możemy dodać metody do ProductsDataTable lub BLL, które wykonują serię poleceń pod parasolem transakcji. Poniższa metoda używa wzorca aktualizacji usługi Batch do aktualizowania ProductsDataTable wystąpienia przy użyciu transakcji. Uruchamia transakcję przez wywołanie BeginTransaction metody , a następnie używa try...catch bloku do wystawiania instrukcji modyfikacji danych. Jeśli wywołanie Adapter metody obiektu Update powoduje wyjątek, wykonanie zostanie przeniesione do catch bloku, w którym transakcja zostanie wycofana, a wyjątek zostanie ponownie zgłoszony. Pamiętaj, że Update metoda implementuje wzorzec aktualizacji usługi Batch, wyliczając wiersze dostarczonego ProductsDataTable elementu i wykonując niezbędne elementy InsertCommand, UpdateCommandi DeleteCommand s. Jeśli którekolwiek z tych poleceń spowoduje wystąpienie błędu, transakcja zostanie wycofana, cofając poprzednie modyfikacje wprowadzone w okresie istnienia transakcji. Jeśli instrukcja zostanie ukończona Update bez błędu, transakcja zostanie zatwierdzona w całości.

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;
    }
}

Dodaj metodę UpdateWithTransaction do ProductsTableAdapter klasy za pomocą klasy częściowej w pliku ProductsTableAdapter.TransactionSupport.cs. Alternatywnie tę metodę można dodać do klasy warstwy ProductsBLL logiki biznesowej z kilkoma drobnymi zmianami składniowymi. Mianowicie słowo kluczowe to w this.BeginTransaction()pliku , this.CommitTransaction()i this.RollbackTransaction() musi zostać zastąpione ciągiem Adapter (przypomnij sobie, że Adapter jest to nazwa właściwości typu ProductsBLL ProductsTableAdapter).

Metoda UpdateWithTransaction używa wzorca aktualizacji usługi Batch, ale serię wywołań DB-Direct można również używać w zakresie transakcji, jak pokazano w poniższej metodzie. Metoda DeleteProductsWithTransaction przyjmuje jako dane wejściowe List<T> typu int, które są do ProductID usunięcia. Metoda inicjuje transakcję za pośrednictwem wywołania metody , BeginTransaction a następnie, w try bloku, iteruje za pośrednictwem podanej listy wywołującej metodę wzorca Delete DB-Direct dla każdej ProductID wartości. Jeśli którekolwiek z wywołań kończy się Delete niepowodzeniem, kontrolka jest przenoszona do catch bloku, w którym transakcja jest cofana, a wyjątek jest zgłaszany ponownie. Jeśli wszystkie wywołania kończą się Delete powodzeniem, transakcja zostanie zatwierdzona. Dodaj tę metodę ProductsBLL do klasy.

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;
    }
}

Stosowanie transakcji między wieloma elementami TableAdapters

Kod związany z transakcjami przeanalizowany w tym samouczku umożliwia traktowanie wielu instrukcji jako ProductsTableAdapter operacji niepodzielnej. Ale co zrobić, jeśli należy wykonać niepodzielne modyfikacje różnych tabel bazy danych? Na przykład podczas usuwania kategorii możemy najpierw ponownie przypisać swoje bieżące produkty do innej kategorii. Te dwa kroki umożliwiają ponowne przypisanie produktów i usunięcie kategorii powinno zostać wykonane jako operacja niepodzielna. Jednak zawiera ProductsTableAdapter tylko metody modyfikowania Products tabeli i CategoriesTableAdapter zawiera tylko metody modyfikowania Categories tabeli. Jak więc transakcja może obejmować obie tabele TableAdapters?

Jedną z opcji jest dodanie metody do CategoriesTableAdapter nazwanej DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID) metody i wywołanie procedury składowanej, która ponownie przypisuje produkty i usuwa kategorię w zakresie transakcji zdefiniowanej w procedurze składowanej. Przyjrzymy się sposobom rozpoczynania, zatwierdzania i wycofywania transakcji w procedurach składowanych w przyszłym samouczku.

Inną opcją jest utworzenie klasy pomocniczej w klasie DAL zawierającej metodę DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID) . Ta metoda utworzy wystąpienie CategoriesTableAdapter klasy i , ProductsTableAdapter a następnie ustawi te dwie właściwości TableAdapters Connection na to samo SqlConnection wystąpienie. W tym momencie jeden z dwóch elementów TableAdapters zainicjowałby transakcję za pomocą wywołania metody BeginTransaction. Metody TableAdapters służące do ponownego przypisywania produktów i usuwania kategorii będą wywoływane w try...catch bloku z zatwierdzoną transakcją lub wycofaną w razie potrzeby.

Krok 4. DodawanieUpdateWithTransactionmetody do warstwy logiki biznesowej

W kroku 3 dodaliśmy metodę UpdateWithTransaction do ProductsTableAdapter tabeli w dal. Powinniśmy dodać odpowiednią metodę do BLL. Chociaż warstwa prezentacji może wywołać metodę bezpośrednio do dal, aby wywołać UpdateWithTransaction metodę, te samouczki starały się zdefiniować architekturę warstwową, która izoluje dal od warstwy prezentacji. W związku z tym zaleca się kontynuowanie tego podejścia.

ProductsBLL Otwórz plik klasy i dodaj metodę o nazwie UpdateWithTransaction , która po prostu wywołuje odpowiednią metodę DAL. Teraz powinny istnieć dwie nowe metody w pliku ProductsBLL: UpdateWithTransaction, które właśnie dodano, i DeleteProductsWithTransaction, które zostały dodane w kroku 3.

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;
    }
}

Uwaga

Te metody nie zawierają atrybutu przypisanego DataObjectMethodAttribute do większości innych metod w ProductsBLL klasie, ponieważ będziemy wywoływać te metody bezpośrednio z klas ASP.NET stron kodowych. Pamiętaj, że DataObjectMethodAttribute służy do flagowania metod, które powinny być wyświetlane w kreatorze Konfigurowanie źródła danych ObjectDataSource i na jakiej karcie (SELECT, UPDATE, INSERT lub DELETE). Ponieważ w kodzie GridView brakuje wbudowanej obsługi edycji lub usuwania wsadowego, będziemy musieli wywołać te metody programowo, a nie użyć podejścia deklaratywnego bez użycia kodu.

Krok 5. Niepodzielne aktualizowanie danych bazy danych z warstwy prezentacji

Aby zilustrować efekt transakcji podczas aktualizowania partii rekordów, utwórzmy interfejs użytkownika, który wyświetla listę wszystkich produktów w kontrolce GridView i zawiera kontrolkę sieci Web przycisku, która po kliknięciu ponownie przypisuje wartości produktów CategoryID . W szczególności ponowne przypisanie kategorii będzie przebiegać tak, aby pierwsze kilka produktów zostało przypisanych do prawidłowej CategoryID wartości, podczas gdy inne celowo przypisano nieistniejącą CategoryID wartość. Jeśli spróbujemy zaktualizować bazę danych przy użyciu produktu, którego CategoryID nie pasuje do istniejącej kategorii s CategoryID, wystąpi naruszenie ograniczenia klucza obcego i zostanie zgłoszony wyjątek. W tym przykładzie zobaczymy, że w przypadku korzystania z transakcji wyjątek zgłoszony przez naruszenie ograniczenia klucza obcego spowoduje wycofanie poprzednich prawidłowych CategoryID zmian. Jeśli jednak nie używasz transakcji, modyfikacje początkowych kategorii pozostaną.

Zacznij od otwarcia Transactions.aspx strony w folderze BatchData i przeciągnięcia kontrolki GridView z przybornika do projektanta. Ustaw wartość ID i Products , z tagu inteligentnego, powiąż go z nowym obiektem ObjectDataSource o nazwie ProductsDataSource. Skonfiguruj obiekt ObjectDataSource, aby ściągnąć dane z ProductsBLL metody klasy s GetProducts . Będzie to widok GridView tylko do odczytu, dlatego ustaw listy rozwijane na kartach UPDATE, INSERT i DELETE na wartość (Brak), a następnie kliknij przycisk Zakończ.

Rysunek 5. Konfigurowanie obiektu ObjectDataSource do używania metody GetProducts klasy ProductsBLL

Rysunek 5. Rysunek 5. Konfigurowanie obiektu ObjectDataSource do używania ProductsBLL GetProducts metody klasy (kliknij, aby wyświetlić obraz pełnowymiarowy)

Ustaw listy rozwijane na kartach UPDATE, INSERT i DELETE na wartość (Brak)

Rysunek 6. Ustawianie list rozwijanych na kartach UPDATE, INSERT i DELETE na wartość (Brak) (Kliknij, aby wyświetlić obraz o pełnym rozmiarze)

Po ukończeniu pracy kreatora Konfigurowanie źródła danych program Visual Studio utworzy pola BoundFields i CheckBoxField dla pól danych produktu. Usuń wszystkie te pola z wyjątkiem ProductIDwłaściwości , , CategoryIDProductName, i oraz CategoryName zmień nazwy ProductName CategoryName właściwości i BoundFields HeaderText odpowiednio na Product i Category. W tagu inteligentnym zaznacz opcję Włącz stronicowanie. Po wprowadzeniu tych modyfikacji znaczniki deklaratywne gridView i ObjectDataSource powinny wyglądać następująco:

<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>

Następnie dodaj trzy kontrolki sieci Web przycisków powyżej kontrolki GridView. Ustaw pierwszą właściwość Text przycisku na Odśwież siatkę, drugą na Modyfikuj kategorie (WITH TRANSACTION), a trzecią na Modyfikuj kategorie (BEZ TRANSAKCJI).

<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>

W tym momencie widok Projektu w programie Visual Studio powinien wyglądać podobnie do zrzutu ekranu pokazanego na rysunku 7.

Strona zawiera kontrolkę GridView i trzy kontrolki sieci Web przycisków

Rysunek 7. Strona zawiera kontrolki Sieci Web GridView i Trzy przyciski (kliknij, aby wyświetlić obraz pełnowymiarowy)

Utwórz programy obsługi zdarzeń dla każdego z trzech zdarzeń przycisków Click i użyj następującego kodu:

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();
}

Procedura obsługi zdarzeń przycisku odświeżania Click po prostu ponownie powiąż dane z kontrolką GridView, wywołując metodę Products GridView DataBind .

Druga procedura obsługi zdarzeń ponownie przypisuje produkty CategoryID i używa nowej metody transakcji z BLL do wykonywania aktualizacji bazy danych pod parasolem transakcji. Należy pamiętać, że każdy produkt CategoryID jest arbitralnie ustawiony na tę samą wartość co jego ProductID. Będzie to działać dobrze w przypadku pierwszych kilku produktów, ponieważ te produkty mają ProductID wartości, które mają miejsce mapowania na prawidłowe CategoryID s. Ale gdy ProductID zacznie się zbyt duży, to przypadkowe nakładanie się ProductID s i CategoryID nie ma już zastosowania.

Trzeci Click program obsługi zdarzeń aktualizuje produkty CategoryID w ten sam sposób, ale wysyła aktualizację do bazy danych przy użyciu metody domyślnej ProductsTableAdapter Update . Ta Update metoda nie opakowuje serii poleceń w ramach transakcji, dlatego te zmiany są wprowadzane przed pierwszym napotkanym błędem naruszenia ograniczenia klucza obcego będzie się powtarzać.

Aby zademonstrować to zachowanie, odwiedź tę stronę za pośrednictwem przeglądarki. Początkowo powinna zostać wyświetlona pierwsza strona danych, jak pokazano na rysunku 8. Następnie kliknij przycisk Modyfikuj kategorie (WITH TRANSACTION). Spowoduje to powrót i próbę zaktualizowania wszystkich wartości produktów CategoryID , ale spowoduje naruszenie ograniczenia klucza obcego (patrz Rysunek 9).

Produkty są wyświetlane w stronicowalnym kącie GridView

Rysunek 8. Produkty są wyświetlane w widoku GridView z możliwością stronicowania (kliknij, aby wyświetlić obraz o pełnym rozmiarze)

Ponowne przypisywanie kategorii powoduje naruszenie ograniczenia klucza obcego

Rysunek 9. Ponowne przypisywanie kategorii powoduje naruszenie ograniczenia klucza obcego (kliknij, aby wyświetlić obraz pełnowymiarowy)

Teraz naciśnij przycisk Wstecz przeglądarki, a następnie kliknij przycisk Odśwież siatkę. Podczas odświeżania danych powinny być widoczne dokładnie te same dane wyjściowe, jak pokazano na rysunku 8. Oznacza to, że mimo że niektóre produkty CategoryID zostały zmienione na wartości prawne i zaktualizowane w bazie danych, zostały one wycofane po wystąpieniu naruszenia ograniczenia klucza obcego.

Teraz spróbuj kliknąć przycisk Modyfikuj kategorie (BEZ TRANSAKCJI). Spowoduje to błąd naruszenia ograniczenia tego samego klucza obcego (patrz Rysunek 9), ale tym razem te produkty, których CategoryID wartości zostały zmienione na wartość prawną, nie zostaną wycofane. Naciśnij przycisk Wstecz przeglądarki, a następnie przycisk Odśwież siatkę. Jak pokazano na rysunku 10, CategoryID liczba pierwszych ośmiu produktów została ponownie przypisana. Na przykład na rysunku 8 Chang miał CategoryID wartość 1, ale na rysunku 10 został ponownie przydzielony do 2.

Niektóre wartości CategoryID produktów zostały zaktualizowane, podczas gdy inne nie były

Rysunek 10. Niektóre wartości produktów CategoryID zostały zaktualizowane, podczas gdy inne nie (kliknij, aby wyświetlić obraz o pełnym rozmiarze)

Podsumowanie

Domyślnie metody TableAdapter nie opakowują wykonanych instrukcji bazy danych w zakresie transakcji, ale przy odrobinie pracy możemy dodać metody, które spowodują utworzenie, zatwierdzenie i wycofanie transakcji. W tym samouczku utworzyliśmy trzy takie metody w ProductsTableAdapter klasie: BeginTransaction, CommitTransactioni RollbackTransaction. Zobaczyliśmy, jak używać tych metod wraz z blokiem try...catch w celu utworzenia serii instrukcji modyfikacji danych niepodzielnych. W szczególności utworzyliśmy metodę UpdateWithTransaction w ProductsTableAdapterpliku , która używa wzorca aktualizacji usługi Batch do wykonania niezbędnych modyfikacji wierszy dostarczonego ProductsDataTableelementu . Dodaliśmy również metodę DeleteProductsWithTransaction do ProductsBLL klasy w usłudze List ProductID BLL, która akceptuje wartości jako dane wejściowe i wywołuje metodę Delete wzorca DB-Direct dla każdego ProductIDelementu . Obie metody zaczynają się od utworzenia transakcji, a następnie wykonania instrukcji modyfikacji danych w try...catch bloku. Jeśli wystąpi wyjątek, transakcja zostanie wycofana, w przeciwnym razie zostanie zatwierdzona.

Krok 5 ilustrował efekt aktualizacji wsadowych transakcyjnych w porównaniu z aktualizacjami wsadowym, które zaniedbywały użycie transakcji. W następnych trzech samouczkach utworzymy podstawy określone w tym samouczku i utworzymy interfejsy użytkownika do wykonywania aktualizacji, usuwania i wstawiania wsadowego.

Szczęśliwe programowanie!

Dalsze informacje

Aby uzyskać więcej informacji na temat tematów omówionych w tym samouczku, zapoznaj się z następującymi zasobami:

Informacje o autorze

Scott Mitchell, autor siedmiu książek ASP/ASP.NET i założyciel 4GuysFromRolla.com, współpracuje z technologiami internetowymi firmy Microsoft od 1998 roku. Scott pracuje jako niezależny konsultant, trener i pisarz. Jego najnowsza książka to Sams Teach Yourself ASP.NET 2.0 w 24 godzinach. Można go uzyskać pod adresem mitchell@4GuysFromRolla.com. lub za pośrednictwem swojego bloga, który można znaleźć na stronie http://ScottOnWriting.NET.

Specjalne podziękowania

Ta seria samouczków została omówiona przez wielu przydatnych recenzentów. Recenzenci w tym samouczku to Dave Gardner, Hilton Giesenow i Teresa Murphy. Chcesz przejrzeć nadchodzące artykuły MSDN? Jeśli tak, upuść mi wiersz pod adresem mitchell@4GuysFromRolla.com.