Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
In un ambiente multiutente sono disponibili due modelli per l'aggiornamento dei dati in un database: concorrenza ottimistica e concorrenza pessimistica. L'oggetto DataSet è progettato per incoraggiare l'uso della concorrenza ottimistica per le attività a esecuzione prolungata, come il trasferimento remoto dei dati e l'interazione con i dati.
La concorrenza pessimistica comporta il blocco delle righe nell'origine dati per impedire ad altri utenti di modificare i dati che possano influenzare l'utente corrente. In un modello pessimistico, quando un utente esegue un'azione che causa l'applicazione di un blocco, altri utenti non possono eseguire azioni in conflitto con il blocco finché il proprietario del blocco non lo rilascia. Questo modello viene usato principalmente negli ambienti in cui è presente un elevato conflitto per i dati, in modo che il costo della protezione dei dati con blocchi sia inferiore al costo del rollback delle transazioni in caso di conflitti di concorrenza.
Pertanto, in un modello di concorrenza pessimistico, un utente che aggiorna una riga stabilisce un blocco. Fino a quando l'utente non ha terminato l'aggiornamento e rilasciato il blocco, nessun altro può modificare tale riga. Per questo motivo, la concorrenza pessimistica viene implementata al meglio quando i tempi di blocco saranno brevi, come nell'elaborazione programmatica dei record. La concorrenza pessimistica non è un'opzione scalabile quando gli utenti interagiscono con i dati e causano il blocco dei record per periodi di tempo relativamente grandi.
Annotazioni
Se è necessario aggiornare più righe nella stessa operazione, la creazione di una transazione è un'opzione più scalabile rispetto all'uso del blocco pessimistico.
Al contrario, gli utenti che usano la concorrenza ottimistica non bloccano una riga durante la lettura. Quando un utente vuole aggiornare una riga, l'applicazione deve determinare se un altro utente ha modificato la riga dopo la lettura. La concorrenza ottimistica viene in genere usata in ambienti con una contesa bassa per i dati. La concorrenza ottimistica migliora le prestazioni perché non è necessario bloccare i record e il blocco dei record richiede risorse server aggiuntive. Inoltre, per mantenere i blocchi dei record, è necessaria una connessione permanente al server di database. Poiché non si tratta del caso in un modello di concorrenza ottimistica, le connessioni al server sono libere di gestire un numero maggiore di client in meno tempo.
In un modello di concorrenza ottimistica, viene considerata una violazione se, dopo che un utente riceve un valore dal database, un altro utente modifica il valore prima che il primo utente abbia tentato di modificarlo. Il modo in cui il server risolve una violazione di concorrenza viene illustrato meglio descrivendo l'esempio seguente.
Le tabelle seguenti seguono un esempio di concorrenza ottimistica.
Alle 13:00 User1 legge una riga dal database con i valori seguenti:
CustID LastName FirstName
101 Smith Bob
Nome della colonna | Valore originale | Valore corrente | Valore nel database |
---|---|---|---|
CustID | 101 | 101 | 101 |
Cognome | Fabbro | Fabbro | Fabbro |
Nome di battesimo | Bob | Bob | Bob |
Alle 13:01, User2 legge la stessa riga.
Alle 13:03 User2 modifica FirstName da "Bob" a "Robert" e aggiorna il database.
Nome della colonna | Valore originale | Valore corrente | Valore nel database |
---|---|---|---|
CustID | 101 | 101 | 101 |
Cognome | Fabbro | Fabbro | Fabbro |
Nome di battesimo | Bob | Roberto | Bob |
L'aggiornamento ha esito positivo perché i valori nel database al momento dell'aggiornamento corrispondono ai valori originali di User2.
Alle 13:05, User1 modifica il nome di "Bob" in "James" e tenta di aggiornare la riga.
Nome della colonna | Valore originale | Valore corrente | Valore nel database |
---|---|---|---|
CustID | 101 | 101 | 101 |
Cognome | Fabbro | Fabbro | Fabbro |
Nome di battesimo | Bob | James | Roberto |
A questo punto, User1 rileva una violazione della concorrenza ottimistica perché il valore nel database ("Robert") non corrisponde più al valore originale previsto da User1 ("Bob"). La violazione della concorrenza consente semplicemente di sapere che l'aggiornamento non è riuscito. La decisione deve ora essere presa se sovrascrivere le modifiche fornite da User2 con le modifiche fornite da User1 o annullare le modifiche apportate da User1.
Test della presenza di violazioni della concorrenza ottimistica
Esistono diverse tecniche per testare una violazione della concorrenza ottimistica. Uno implica l'inclusione di una colonna timestamp nella tabella. I database forniscono in genere funzionalità di timestamp che possono essere usate per identificare la data e l'ora dell'ultimo aggiornamento del record. Usando questa tecnica, nella definizione della tabella viene inclusa una colonna timestamp. Ogni volta che il record viene aggiornato, il timestamp viene aggiornato in modo da riflettere la data e l'ora correnti. In un test sulle violazioni della concorrenza ottimistica, la colonna denominata "timestamp" viene inclusa in qualsiasi query sui dati della tabella. Quando si tenta un aggiornamento, il valore timestamp nel database viene confrontato con il valore timestamp originale contenuto nella riga modificata. Se corrispondono, l'aggiornamento viene eseguito e la colonna timestamp viene aggiornata con l'ora corrente per riflettere l'aggiornamento. Se non corrispondono, si è verificata una violazione della concorrenza ottimistica.
Un'altra tecnica per testare una violazione della concorrenza ottimistica consiste nel verificare che tutti i valori di colonna originali in una riga corrispondano ancora a quelli trovati nel database. Ad esempio, si consideri la query seguente:
SELECT Col1, Col2, Col3 FROM Table1
Per verificare una violazione della concorrenza ottimistica durante l'aggiornamento di una riga in Table1, dovresti eseguire la seguente istruzione UPDATE:
UPDATE Table1 Set Col1 = @NewCol1Value,
Set Col2 = @NewCol2Value,
Set Col3 = @NewCol3Value
WHERE Col1 = @OldCol1Value AND
Col2 = @OldCol2Value AND
Col3 = @OldCol3Value
Se i valori originali corrispondono ai valori nel database, viene eseguito l'aggiornamento. Se un valore è stato modificato, l'aggiornamento non modificherà la riga perché la clausola WHERE non troverà una corrispondenza.
Si noti che è consigliabile restituire sempre un valore di chiave primaria univoco nella query. In caso contrario, l'istruzione UPDATE precedente può aggiornare più di una riga, che potrebbe non essere la finalità.
Se una colonna nell'origine dati consente valori Null, potrebbe essere necessario estendere la clausola WHERE per verificare la presenza di un riferimento Null corrispondente nella tabella locale e nell'origine dati. Ad esempio, l'istruzione UPDATE seguente verifica che un riferimento Null nella riga locale corrisponda ancora a un riferimento Null nell'origine dati o che il valore nella riga locale corrisponda ancora al valore nell'origine dati.
UPDATE Table1 Set Col1 = @NewVal1
WHERE (@OldVal1 IS NULL AND Col1 IS NULL) OR Col1 = @OldVal1
È anche possibile scegliere di applicare criteri meno restrittivi quando si usa un modello di concorrenza ottimistica. Se ad esempio si usano solo le colonne chiave primaria nella clausola WHERE, i dati vengono sovrascritti indipendentemente dal fatto che le altre colonne siano state aggiornate dopo l'ultima query. È anche possibile applicare una clausola WHERE solo a colonne specifiche, determinando la sovrascrittura dei dati, a meno che non siano stati aggiornati campi specifici dall'ultima query.
Evento DataAdapter.RowUpdated
L'evento RowUpdated dell'oggetto DataAdapter può essere usato insieme alle tecniche descritte in precedenza, per fornire notifiche all'applicazione di violazioni della concorrenza ottimistica. RowUpdated si verifica dopo ogni tentativo di aggiornare una riga modificata da un oggetto DataSet. In questo modo è possibile aggiungere codice di gestione speciale, inclusa l'elaborazione quando si verifica un'eccezione, l'aggiunta di informazioni di errore personalizzate, l'aggiunta della logica di ripetizione dei tentativi e così via. L'oggetto RowUpdatedEventArgs restituisce una proprietà RecordsAffected contenente il numero di righe interessate da un comando di aggiornamento specifico per una riga modificata in una tabella. Impostando il comando di aggiornamento per verificare la concorrenza ottimistica, la proprietà RecordsAffected restituirà quindi un valore pari a 0 quando si è verificata una violazione della concorrenza ottimistica, perché non sono stati aggiornati record. In questo caso, viene sollevata un'eccezione. L'evento RowUpdated consente di gestire questa occorrenza ed evitare l'eccezione impostando un valore RowUpdatedEventArgs.Status appropriato, ad esempio UpdateStatus.SkipCurrentRow. Per altre informazioni sull'evento RowUpdated , vedere Gestione degli eventi DataAdapter.
Facoltativamente, è possibile impostare DataAdapter.ContinueUpdateOnError su true, prima di chiamare Update e rispondere alle informazioni sull'errore archiviate nella proprietà RowError di una determinata riga al termine dell'aggiornamento. Per altre informazioni, vedere Informazioni sull'errore di riga.
Esempio di concorrenza ottimistica
Di seguito è riportato un semplice esempio che imposta il UpdateCommand di un DataAdapter per verificare la concorrenza ottimistica, e quindi utilizza l'evento RowUpdated per verificare le violazioni della concorrenza ottimistica. Quando viene rilevata una violazione della concorrenza ottimistica, l'applicazione imposta rowError della riga per cui è stato emesso l'aggiornamento per riflettere una violazione della concorrenza ottimistica.
Si noti che i valori dei parametri passati alla clausola WHERE del comando UPDATE vengono mappati ai valori Originali delle rispettive colonne.
' Assumes connection is a valid SqlConnection.
Dim adapter As SqlDataAdapter = 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 " &
"(CustomerID, CompanyName) VALUES(@CustomerID, @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.
Dim parameter As SqlParameter = 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.
AddHandler adapter.RowUpdated, New SqlRowUpdatedEventHandler( _
AddressOf OnRowUpdated)
Dim dataSet As DataSet = New DataSet()
adapter.Fill(dataSet, "Customers")
' Modify the DataSet contents.
adapter.Update(dataSet, "Customers")
Dim dataRow As DataRow
For Each dataRow In dataSet.Tables("Customers").Rows
If dataRow.HasErrors Then
Console.WriteLine(dataRow (0) & vbCrLf & dataRow.RowError)
End If
Next
Private Shared Sub OnRowUpdated( _
sender As object, args As SqlRowUpdatedEventArgs)
If args.RecordsAffected = 0
args.Row.RowError = "Optimistic Concurrency Violation!"
args.Status = UpdateStatus.SkipCurrentRow
End If
End Sub
// 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;
}
}