Opakowywanie modyfikacji bazy danych w ramach transakcji (C#)

Autor : Scott Mitchell

Pobierz plik PDF

Ten samouczek jest pierwszym 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 zakończyły się powodzeniem lub wszystkimi krokami.

Wprowadzenie

Jak widzieliśmy, począwszy od samouczka Omówienie wstawiania, aktualizowania i usuwania danych , kontrolka 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, o ile 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, aby wyświetlić listę każdej wiadomości, w której każdy wiersz zawiera pole wyboru wraz z informacjami o wiadomościach e-mail (temat, nadawca itd.). Ten interfejs pozwala użytkownikowi usunąć wiele 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 przycisku Edytuj, wprowadź zmiany, a następnie kliknij przycisk Aktualizuj dla każdego rekordu, który należy zmodyfikować, 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 należy określić, czy niektóre operacje w partii powinny być możliwe, aby zakończyły się powodzeniem, podczas 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 została potraktowana 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 w ramach 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 przeanalizują implementację stron internetowych pod kątem wstawiania, aktualizowania i usuwania interfejsów wsadowych. Zacznijmy!

Uwaga

Podczas modyfikowania danych w transakcji wsadowej niepodzielność nie zawsze jest potrzebna. W niektórych scenariuszach może być akceptowalne, że niektóre modyfikacje danych kończą się powodzeniem, 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ąpi błąd bazy danych, prawdopodobnie dopuszczalne jest, aby te rekordy przetwarzane bez błędów pozostały usunięte. W takich przypadkach funkcja 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 krokowi odnieść sukces, ale drugi krok nie powiedzie się, jego klienci zrozumiale byliby zdenerwowani. Zachęcam do pracy z tym samouczkiem i zaimplementowania ulepszeń do obsługi transakcji bazy danych, nawet jeśli nie planujesz ich używać w partiach wstawiania, aktualizowania i usuwania interfejsów, które będziemy kompilować w kolejnych trzech samouczkach.

Omówienie transakcji

Większość baz danych obejmuje obsługę transakcji, które umożliwiają grupowanie wielu poleceń bazy danych w jedną jednostkę logiczną pracy. Polecenia bazy danych składające się z transakcji są gwarantowane jako 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 służące do tworzenia, zatwierdzania i wycofywania transakcji można wprowadzać 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

KlasaTransactionScope 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 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 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 witryną MSDTC sprawiają, że jest to dość wyspecjalizowany i zaawansowany temat oraz wykracza poza zakres tych samouczków.

Podczas pracy z dostawcą SqlClient w ADO.NET transakcje są inicjowane za pośrednictwem wywołania SqlConnection metody klasy, BeginTransactionktóra zwraca SqlTransaction obiekt. Instrukcje modyfikacji danych, które makijaż transakcji są umieszczane w try...catch bloku. Jeśli wystąpi błąd w instrukcji w try bloku, wykonywanie jest transferowane do catch bloku, w którym transakcja może zostać wycofana za pośrednictwem SqlTransaction metody object sRollback. Jeśli wszystkie instrukcje zakończą się pomyślnie, wywołanie SqlTransaction metody obiektu Commit na końcu try bloku zatwierdza transakcję. Poniższy fragment kodu ilustruje ten wzorzec. Zobacz Zachowywanie 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 stron sieci Web danych wsadowych

Zanim zaczniemy eksplorować sposób rozszerzania dal w celu obsługi transakcji bazy danych, najpierw poświęćmy chwilę na utworzenie ASP.NET stron internetowych, które będą potrzebne 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 SqlDataSource-Related

Rysunek 1. Dodawanie stron ASP.NET dla samouczków SqlDataSource-Related

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ę użytkownika, Default.aspx przeciągając ją z Eksplorator rozwiązań na widok projektowy strony.

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

Rysunek 2. Dodawanie kontrolki SectionLevelTutorialListing.ascx użytkownika do (Default.aspxkliknij, aby wyświetlić obraz pełnowymiarowy)

Na koniec dodaj te cztery strony jako wpisy do Web.sitemap pliku. W szczególności dodaj następujące znaczniki 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święć 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 samouczkami dotyczącymi danych wsadowych

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ż wspomniano w pierwszym samouczku, tworzenie warstwy dostępu do danych, typizowane zestawy danych w naszym dal składają 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 za pomocą zmian wprowadzonych 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 elementów DataRows. Te dane są wyliczane i są wykonywane dla każdego wstawionego, zmodyfikowanego lub usuniętego InsertCommandUpdateCommandwiersza , lub DeleteCommand . W przypadku wzorca DB-Direct element TableAdapter jest zamiast tego przekazywany wartości kolumn niezbędnych do wstawiania, aktualizowania lub usuwania pojedynczego rekordu. Następnie metoda wzorca DB Direct używa tych przekazanych wartości do wykonania odpowiedniej InsertCommandinstrukcji , UpdateCommandlub DeleteCommand .

Niezależnie od używanego wzorca aktualizacji metody generowane automatycznie nie używają transakcji. Domyślnie każda operacja wstawiania, aktualizowania lub usuwania wykonywana przez metodę TableAdapter jest traktowana jako pojedyncza dyskretna operacja. Załóżmy na przykład, że wzorzec DB-Direct jest używany przez 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 napotkano 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ą InsertCommand, UpdateCommandi DeleteCommand s pod parasolem transakcji. W temacie Tworzenie warstwy dostępu do danych przyjrzeliśmy się używaniu klas częściowych do rozszerzania funkcjonalności tabel DataTable w ramach typizowanego 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 TransactionSupport i dodaj nowy plik klasy o nazwie ProductsTableAdapter.TransactionSupport.cs (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 TransactionSupport i pliku klasy o nazwie ProductsTableAdapter.TransactionSupport.cs

Wprowadź następujący kod do 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 tutaj 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ż klasa TableAdapter została skonfigurowana 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ż Microsoft SQL Server, musisz użyć odpowiedniego dostawcy.

Te metody udostępniają bloki konstrukcyjne potrzebne do uruchamiania, wycofywania i zatwierdzania transakcji. Są one oznaczone , publicumożliwiając ich korzystanie z poziomu ProductsTableAdapterklasy z innej klasy w dal lub z innej warstwy w architekturze, takiej jak BLL. BeginTransaction Otwiera wewnętrzną SqlConnection tabelę TableAdapter (w razie potrzeby), rozpoczyna transakcję i przypisuje ją do Transaction właściwości oraz dołącza transakcję do obiektów wewnętrznych SqlDataAdapterSqlCommand . CommitTransaction i RollbackTransaction wywołać Transaction odpowiednio metody Commit i Rollback obiektów przed zamknięciem obiektu wewnętrznego Connection .

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 jedno 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 jest zatwierdzana 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 (pamiętaj, że Adapter jest to nazwa właściwości typu ProductsBLLProductsTableAdapter).

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ą elementami ProductID do usunięcia. Metoda inicjuje transakcję za pośrednictwem wywołania metody BeginTransaction , a następnie w try bloku wykonuje iterację po podanej liście wywołującej metodę wzorca Delete DB-Direct dla każdej ProductID wartości. Jeśli którekolwiek z wywołań zakończy się Delete niepowodzeniem, kontrolka zostanie przeniesiona do catch bloku, w którym transakcja zostanie wycofana, a wyjątek zostanie ponownie zgłoszony. 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 w wielu elementach TableAdapters

Kod związany z transakcjami przeanalizowany w tym samouczku umożliwia traktowanie wielu instrukcji jako ProductsTableAdapter operacji niepodzielnej. Ale co zrobić, jeśli trzeba wykonać wiele modyfikacji w różnych tabelach bazy danych niepodziealnie? Na przykład podczas usuwania kategorii możemy najpierw ponownie przypisać swoje bieżące produkty do innej kategorii. Te dwa kroki powodują ponowne przypisanie produktów i usunięcie kategorii powinno zostać wykonane jako operacja niepodzielna. Jednak metoda ProductsTableAdapter zawiera tylko metody modyfikowania Products tabeli i CategoriesTableAdapter zawiera tylko metody modyfikowania Categories tabeli. Więc jak transakcja może obejmować zarówno TableAdapters?

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

Inną opcją jest utworzenie klasy pomocniczej w klasie DAL zawierającej metodę DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID) . Ta metoda tworzy wystąpienie klasy CategoriesTableAdapter i ProductsTableAdapter , a następnie ustawia te dwie właściwości Klasy 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 bloku z zatwierdzoną lub wycofaną transakcją zgodnie try...catch z potrzebami.

Krok 4. DodawanieUpdateWithTransactionmetody do warstwy logiki biznesowej

W kroku 3 dodaliśmy metodę UpdateWithTransaction do ProductsTableAdapter metody w dal. Należy dodać odpowiednią metodę do BLL. Chociaż warstwa prezentacji może wywołać metodę bezpośrednio do warstwy DAL, aby wywołać UpdateWithTransaction metodę, te samouczki mają na celu zdefiniowanie architektury warstwowej, 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 metodę DAL w dół do odpowiedniej metody 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 obejmują 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 nie ma wbudowanej obsługi edycji lub usuwania wsadowego, będziemy musieli wywołać te metody programowo, zamiast korzystać z 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 zostały celowo przypisane nieistniejącej CategoryID wartości. 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 Projektant. Ustaw parametr ID na Products i z tagu inteligentnego, powiąż go z nową wartością ObjectDataSource o nazwie ProductsDataSource. Skonfiguruj obiekt ObjectDataSource, aby ściągnąć dane z ProductsBLL metody klasy s GetProducts . Będzie to kontrolka 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 ProductsBLLGetProducts metody klasy (kliknij, aby wyświetlić obraz pełnowymiarowy)

Ustaw Drop-Down Listy na kartach UPDATE, INSERT i DELETE na wartość (Brak)

Rysunek 6. Ustawianie Drop-Down Listy 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 , , CategoryIDProductNamei CategoryName i ProductNameCategoryName BoundFields HeaderText odpowiednio na Product (Produkt) i Category (Kategoria). 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 przycisku nad kontrolką GridView. Ustaw pierwszą właściwość Text przycisku na Odśwież siatkę, drugą na Modyfikuj kategorie (WITH TRANSACTION), a trzecią z nich 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 Projekt w programie Visual Studio powinien wyglądać podobnie do zrzutu ekranu pokazanego na rysunku 7.

Strona zawiera kontrolki Sieci Web GridView i trzy przyciski

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

Trzecia Click procedura obsługi zdarzeń aktualizuje produkty CategoryID w taki sam sposób, ale wysyła aktualizację do bazy danych przy użyciu metody domyślnej ProductsTableAdapterUpdate . Ta Update metoda nie opakowuje serii poleceń w ramach transakcji, dlatego te zmiany są wprowadzane przed pierwszym napotkanym błędem naruszenia ograniczeń 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 wycofanie i próbę zaktualizowania wszystkich wartości produktów CategoryID , ale spowoduje naruszenie ograniczenia klucza obcego (zobacz Rysunek 9).

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

Rysunek 8. Produkty są wyświetlane w widoku GridView z możliwością stronicowania (kliknij, aby wyświetlić obraz w 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 w pełnym rozmiarze)

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, co 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, gdy doszło do 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 wartości 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 w 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 , aby utworzyć serię instrukcji modyfikacji danych niepodzielne. 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 bibliotece ListProductID 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 zilustrował wpływ transakcyjnych aktualizacji wsadowych w porównaniu z aktualizacjami wsadowym, które nie korzystały z transakcji. W następnych trzech samouczkach będziemy opierać się na podstawach określonych w tym samouczku i utworzyć 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 ciągu 24 godzin. Można do niego dotrzeć 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 przejrzyona przez wielu przydatnych recenzentów. Głównymi recenzentami tego samouczka byli Dave Gardner, Hilton Giesenow i Teresa Murphy. Chcesz przejrzeć nadchodzące artykuły MSDN? Jeśli tak, upuść mi wiersz pod adresemmitchell@4GuysFromRolla.com .