Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Dotyczy: .NET Framework
.NET
Standard
W środowisku wieloużytkownikowym istnieją dwa modele aktualizowania danych w bazie danych: optymistyczna współbieżność i pesymistyczna współbieżność. Obiekt DataSet został zaprojektowany tak, aby zachęcić do używania optymistycznej współbieżności w przypadku długotrwałych działań, takich jak zdalne przesyłanie danych i interakcja z danymi.
Pesymistyczna współbieżność polega na zablokowaniu wierszy w źródle danych, aby uniemożliwić innym użytkownikom modyfikowanie danych w sposób, który ma wpływ na bieżącego użytkownika. W modelu pesymistycznym, gdy użytkownik wykonuje akcję, która powoduje zastosowanie blokady, inni użytkownicy nie mogą wykonywać akcji, które mogłyby powodować konflikt z blokadą, dopóki właściciel blokady go nie zwolni. Ten model jest używany głównie w środowiskach, w których występuje duża rywalizacja o dane, dzięki czemu koszt ochrony danych za pomocą blokad jest mniejszy niż koszt wycofywania transakcji, jeśli wystąpią konflikty współbieżności.
W związku z tym w modelu pesymistycznej współbieżności użytkownik aktualizujący wiersz zakłada blokadę. Dopóki użytkownik nie zakończy aktualizacji i zwolni blokadę, nikt inny nie może zmienić tego wiersza. Z tego powodu pesymistyczna współbieżność jest najlepiej stosowana, gdy czasy blokady są krótkie, jak to ma miejsce w programowym przetwarzaniu rekordów. Pesymistyczna współbieżność nie jest skalowalną opcją, gdy użytkownicy wchodzą w interakcję z danymi i powodują zablokowanie rekordów przez stosunkowo duże okresy czasu.
Uwaga / Notatka
Jeśli musisz zaktualizować wiele wierszy w tej samej operacji, utworzenie transakcji jest bardziej skalowalną opcją niż używanie pesymistycznego blokowania.
Z kolei użytkownicy korzystający z optymistycznej współbieżności nie blokują wiersza podczas odczytywania go. Gdy użytkownik chce zaktualizować wiersz, aplikacja musi określić, czy inny użytkownik zmienił wiersz od czasu jego odczytania. Optymistyczna współbieżność jest zwykle używana w środowiskach o niskiej rywalizacji o dane. Optymistyczna współbieżność zwiększa wydajność, ponieważ nie jest wymagane blokowanie rekordów, a blokowanie rekordów wymaga dodatkowych zasobów serwera. Ponadto w celu zachowania blokad rekordów wymagane jest trwałe połączenie z serwerem bazy danych. Ponieważ tak nie jest w przypadku optymistycznego modelu współbieżności, połączenia z serwerem są bezpłatne do obsługi większej liczby klientów w krótszym czasie.
W modelu optymistycznej współbieżności uważa się, że wystąpiło naruszenie, jeśli po otrzymaniu przez użytkownika wartości z bazy danych inny użytkownik modyfikuje wartość, zanim pierwszy użytkownik próbował go zmodyfikować. Jak serwer rozwiązuje naruszenie współbieżności, najlepiej jest pokazać, opisując najpierw poniższy przykład.
W poniższych tabelach podano przykład dotyczący optymistycznej współbieżności.
O godzinie 13:00 użytkownik User1 odczytuje wiersz z bazy danych z następującymi wartościami:
IdentyfikatorKlienta Nazwisko Imię
101 Smith Bob
| Nazwa kolumny | Oryginalna wartość | Bieżąca wartość | Wartość w bazie danych |
|---|---|---|---|
| Identyfikator CustID | 101 | 101 | 101 |
| Nazwisko | Borkowski | Borkowski | Borkowski |
| Imię (pierwsze) | Robert | Robert | Robert |
O godzinie 13:01 użytkownik User2 odczytuje ten sam wiersz.
O godzinie 13:03 użytkownik User2 zmienia wartość FirstName z "Bob" na "Robert" i aktualizuje bazę danych.
| Nazwa kolumny | Oryginalna wartość | Bieżąca wartość | Wartość w bazie danych |
|---|---|---|---|
| Identyfikator CustID | 101 | 101 | 101 |
| Nazwisko | Borkowski | Borkowski | Borkowski |
| Imię (pierwsze) | Robert | Robert | Robert |
Aktualizacja powiedzie się, ponieważ wartości w bazie danych w czasie aktualizacji są zgodne z oryginalnymi wartościami, które ma użytkownik User2.
O godzinie 13:05 użytkownik User1 zmienia imię "Boba" na "Jamesa" i próbuje zaktualizować wiersz.
| Nazwa kolumny | Oryginalna wartość | Bieżąca wartość | Wartość w bazie danych |
|---|---|---|---|
| Identyfikator CustID | 101 | 101 | 101 |
| Nazwisko | Borkowski | Borkowski | Borkowski |
| Imię (pierwsze) | Robert | James | Robert |
W tym momencie użytkownik User1 napotyka optymistyczne naruszenie współbieżności, ponieważ wartość w bazie danych ("Robert") nie jest już zgodna z oryginalną wartością oczekiwaną przez użytkownika User1 ("Bob"). Naruszenie współbieżności po prostu informuje o tym, że aktualizacja nie powiodła się. Teraz należy podjąć decyzję, czy zastąpić zmiany wprowadzone przez użytkownika User2 zmianami dostarczonymi przez użytkownika User1, czy anulować zmiany według użytkownika User1.
Testowanie pod kątem optymistycznych naruszeń współbieżności
Istnieje kilka technik testowania pod kątem optymistycznego naruszenia współbieżności. Jednym z nich jest dołączenie kolumny znacznika czasu w tabeli.
Bazy danych często udostępniają funkcje sygnatury czasowej, które mogą służyć do identyfikowania daty i godziny ostatniej aktualizacji rekordu. Korzystając z tej techniki, kolumna znacznika czasu jest zawarta w definicji tabeli. Za każdym razem, gdy rekord jest aktualizowany, znacznik czasu jest aktualizowany w celu odzwierciedlenia bieżącej daty i godziny.
W teście pod kątem optymistycznych naruszeń współbieżności kolumna czasowa jest zwracana przy każdym zapytaniu o zawartość tabeli. Podczas próby aktualizacji wartość znacznika czasu w bazie danych jest porównywana z oryginalną wartością znacznika czasu zawartą w zmodyfikowanym wierszu. Jeśli są one zgodne, aktualizacja zostanie wykonana, a kolumna sygnatury czasowej zostanie zaktualizowana o bieżący czas, aby odzwierciedlić aktualizację. Jeśli nie są zgodne, doszło do optymistycznego naruszenia współbieżności.
Inną techniką testowania pod kątem optymistycznego naruszenia współbieżności jest sprawdzenie, czy wszystkie oryginalne wartości kolumn w wierszu nadal pasują do tych znalezionych w bazie danych. Rozważmy na przykład następujące zapytanie:
SELECT Col1, Col2, Col3 FROM Table1
Aby przetestować naruszenie współbieżności optymistycznej podczas aktualizowania wiersza w Table1, należy wydać następującą instrukcję UPDATE:
UPDATE Table1 Set Col1 = @NewCol1Value,
Set Col2 = @NewCol2Value,
Set Col3 = @NewCol3Value
WHERE Col1 = @OldCol1Value AND
Col2 = @OldCol2Value AND
Col3 = @OldCol3Value
Jeśli oryginalne wartości są zgodne z wartościami w bazie danych, aktualizacja jest wykonywana. Jeśli wartość została zmodyfikowana, aktualizacja nie zmodyfikuje wiersza, ponieważ klauzula WHERE nie znajdzie dopasowania.
Należy pamiętać, że zaleca się zawsze zwracanie unikatowej wartości klucza podstawowego w zapytaniu. W przeciwnym razie poprzednia instrukcja UPDATE może zaktualizować więcej niż jeden wiersz, co może nie być twoim zamiarem.
Jeśli kolumna w źródle danych zezwala na wartości null, może być konieczne rozszerzenie klauzuli WHERE w celu sprawdzenia pasującego odwołania o wartości null w tabeli lokalnej i w źródle danych. Na przykład poniższa instrukcja UPDATE sprawdza, czy odwołanie o wartości null w wierszu lokalnym nadal pasuje do odwołania o wartości null w źródle danych lub że wartość w wierszu lokalnym nadal odpowiada wartości w źródle danych.
UPDATE Table1 Set Col1 = @NewVal1
WHERE (@OldVal1 IS NULL AND Col1 IS NULL) OR Col1 = @OldVal1
Możesz również zastosować mniej restrykcyjne kryteria podczas korzystania z optymistycznego modelu współbieżności. Na przykład użycie tylko kolumn klucza podstawowego w klauzuli WHERE powoduje zastąpienie danych niezależnie od tego, czy inne kolumny zostały zaktualizowane od ostatniego zapytania. Można również zastosować klauzulę WHERE tylko do określonych kolumn, co powoduje zastąpienie danych, chyba że określone pola zostały zaktualizowane od czasu ostatniego zapytania.
Zdarzenie DataAdapter.RowUpdated
Zdarzenie RowUpdated obiektu może być używane w połączeniu z opisanymi wcześniej technikami, aby przekazać powiadomienie do aplikacji o naruszeniach współbieżności w trybie optymistycznym. RowUpdated ma miejsce po każdej próbie aktualizacji zmodyfikowanego wiersza z DataSet. Dzięki temu można dodać specjalny kod obsługi, w tym przetwarzanie w przypadku wystąpienia wyjątku, dodawanie niestandardowych informacji o błędach, dodawanie logiki ponawiania itd.
Obiekt RowUpdatedEventArgs zwraca właściwość RecordsAffected zawierającą liczbę wierszy, na które ma wpływ określone polecenie aktualizacji zmodyfikowanego wiersza w tabeli. Ustawiając polecenie aktualizacji w celu przetestowania optymistycznej współbieżności, właściwość RecordsAffected zwróci w rezultacie wartość 0, gdy wystąpiło optymistyczne naruszenie współbieżności, ponieważ nie zaktualizowano żadnych rekordów. W takim przypadku zgłaszany jest wyjątek.
Zdarzenie RowUpdated umożliwia obsługę tego wystąpienia i unikanie wyjątku, ustawiając odpowiednią wartość RowUpdatedEventArgs.Status , taką jak UpdateStatus.SkipCurrentRow. Aby uzyskać więcej informacji na temat zdarzenia RowUpdated , zobacz Obsługa zdarzeń DataAdapter.
Opcjonalnie możesz ustawić właściwość DataAdapter.ContinueUpdateOnError na wartość true, przed wywołaniem polecenia Update i odpowiedzieć na informacje o błędzie przechowywane we właściwości RowError określonego wiersza po zakończeniu aktualizacji . Aby uzyskać więcej informacji, zobacz Informacje o błędach wiersza.
Przykład optymistycznej współbieżności
Poniżej przedstawiono prosty przykład, który ustawia polecenie UpdateCommand elementu DataAdapter do testowania pod kątem optymistycznej współbieżności, a następnie używa zdarzenia RowUpdated do testowania pod kątem optymistycznych naruszeń współbieżności. Po napotkaniu optymistycznego naruszenia współbieżności aplikacja ustawia wartość RowError wiersza, dla którego wydano aktualizację, aby odzwierciedlić optymistyczne naruszenie współbieżności.
Należy pamiętać, że wartości parametrów przekazane do klauzuli WHERE polecenia UPDATE są mapowane na oryginalne wartości odpowiednich kolumn.
using System;
using System.Data;
using Microsoft.Data.SqlClient;
class Program
{
static void Main(string[] args)
{
string connectionString = "Data Source = localhost; Integrated Security = true; Initial Catalog = Northwind";
using (SqlConnection connection = new SqlConnection(connectionString))
{
// Assumes connection is a valid SqlConnection.
SqlDataAdapter adapter = new SqlDataAdapter(
"SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID",
connection);
// The Update command checks for optimistic concurrency violations
// in the WHERE clause.
adapter.UpdateCommand = new SqlCommand("UPDATE Customers Set CustomerID = @CustomerID, CompanyName = @CompanyName " +
"WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName", connection);
adapter.UpdateCommand.Parameters.Add(
"@CustomerID", SqlDbType.NChar, 5, "CustomerID");
adapter.UpdateCommand.Parameters.Add(
"@CompanyName", SqlDbType.NVarChar, 30, "CompanyName");
// Pass the original values to the WHERE clause parameters.
SqlParameter parameter = adapter.UpdateCommand.Parameters.Add(
"@oldCustomerID", SqlDbType.NChar, 5, "CustomerID");
parameter.SourceVersion = DataRowVersion.Original;
parameter = adapter.UpdateCommand.Parameters.Add(
"@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName");
parameter.SourceVersion = DataRowVersion.Original;
// Add the RowUpdated event handler.
adapter.RowUpdated += new SqlRowUpdatedEventHandler(OnRowUpdated);
DataSet dataSet = new DataSet();
adapter.Fill(dataSet, "Customers");
// Modify the DataSet contents.
adapter.Update(dataSet, "Customers");
foreach (DataRow dataRow in dataSet.Tables["Customers"].Rows)
{
if (dataRow.HasErrors)
Console.WriteLine(dataRow[0] + "\n" + dataRow.RowError);
}
}
}
protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs args)
{
if (args.RecordsAffected == 0)
{
args.Row.RowError = "Optimistic Concurrency Violation Encountered";
args.Status = UpdateStatus.SkipCurrentRow;
}
}
}