Opakowywanie modyfikacji bazy danych w ramach transakcji (C#)
Autor: Scott Mitchell
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 INSERT
instrukcji , UPDATE
i 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:
- Wskazuje początek transakcji.
- Wykonaj instrukcje SQL, które składają się na transakcję.
- Jeśli wystąpi błąd w jednej z instrukcji z kroku 2, wycofaj transakcję.
- 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
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.
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.sitemap
programu 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.
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 InsertCommand
wiersza, wykonywane jest polecenie , UpdateCommand
lub 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 InsertCommand
instrukcji , 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ą InsertCommand
metodę , UpdateCommand
i 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.
Rysunek 4. Dodawanie folderu o nazwie i pliku klasy o nazwie TransactionSupport
ProductsTableAdapter.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 ProductsTableAdapter
klasy , 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
. CommitTransaction
i 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
, UpdateCommand
i 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. DodawanieUpdateWithTransaction
metody 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. Rysunek 5. Konfigurowanie obiektu ObjectDataSource do używania ProductsBLL
GetProducts
metody klasy (kliknij, aby wyświetlić obraz pełnowymiarowy)
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 ProductID
właściwości , , CategoryID
ProductName
, 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.
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).
Rysunek 8. Produkty są wyświetlane w widoku GridView z możliwością stronicowania (kliknij, aby wyświetlić obraz o pełnym rozmiarze)
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.
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
, CommitTransaction
i 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 ProductsTableAdapter
pliku , która używa wzorca aktualizacji usługi Batch do wykonania niezbędnych modyfikacji wierszy dostarczonego ProductsDataTable
elementu . 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 ProductID
elementu . 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:
- Transakcje wykonane łatwo:
System.Transactions
- TransactionScope i DataAdapters
- Korzystanie z transakcji bazy danych Oracle na platformie .NET
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.