Simultaneidade Otimista
Em um ambiente multiusuário, existem dois modelos para atualizar dados em um banco de dados: simultaneidade otimista e simultaneidade pessimista. O DataSet objeto foi projetado para incentivar o uso de simultaneidade otimista para atividades de longa duração, como comunicação remota de dados e interação com dados.
A simultaneidade pessimista envolve o bloqueio de linhas na fonte de dados para impedir que outros usuários modifiquem os dados de uma forma que afete o usuário atual. Em um modelo pessimista, quando um usuário executa uma ação que faz com que um bloqueio seja aplicado, outros usuários não podem executar ações que entrariam em conflito com o bloqueio até que o proprietário do bloqueio o libere. Esse modelo é usado principalmente em ambientes onde há grande disputa de dados, de modo que o custo de proteger dados com bloqueios é menor do que o custo de reverter transações se ocorrerem conflitos de simultaneidade.
Portanto, em um modelo de simultaneidade pessimista, um usuário que atualiza uma linha estabelece um bloqueio. Até que o usuário termine a atualização e libere o bloqueio, ninguém mais poderá alterar essa linha. Por esta razão, a simultaneidade pessimista é melhor implementada quando os tempos de bloqueio serão curtos, como no processamento programático de registros. A simultaneidade pessimista não é uma opção escalável quando os usuários estão interagindo com dados e fazendo com que os registros sejam bloqueados por períodos de tempo relativamente grandes.
Nota
Se você precisar atualizar várias linhas na mesma operação, criar uma transação é uma opção mais escalável do que usar o bloqueio pessimista.
Por outro lado, os usuários que usam simultaneidade otimista não bloqueiam uma linha ao lê-la. Quando um usuário deseja atualizar uma linha, o aplicativo deve determinar se outro usuário alterou a linha desde que ela foi lida. A simultaneidade otimista é geralmente usada em ambientes com baixa contenção de dados. A simultaneidade otimista melhora o desempenho porque nenhum bloqueio de registros é necessário e o bloqueio de registros requer recursos adicionais do servidor. Além disso, para manter bloqueios de registro, é necessária uma conexão persistente com o servidor de banco de dados. Como esse não é o caso em um modelo de simultaneidade otimista, as conexões com o servidor são livres para atender a um número maior de clientes em menos tempo.
Em um modelo de simultaneidade otimista, uma violação é considerada como tendo ocorrido se, depois que um usuário recebe um valor do banco de dados, outro usuário modifica o valor antes que o primeiro usuário tenha tentado modificá-lo. Como o servidor resolve uma violação de simultaneidade é melhor mostrado descrevendo primeiro o exemplo a seguir.
As tabelas a seguir seguem um exemplo de simultaneidade otimista.
Às 13:00, User1 lê uma linha do banco de dados com os seguintes valores:
CustID Sobrenome Nome
101 Bob Smith
Nome da coluna | Valor original | Valor atual | Valor na base de dados |
---|---|---|---|
CustID | 101 | 101 | 101 |
LastName | Smith | Smith | Smith |
FirstName | Fernando | Fernando | Fernando |
Às 13h01, User2 lê a mesma linha.
Às 13h03, User2 muda FirstName de "Bob" para "Robert" e atualiza o banco de dados.
Nome da coluna | Valor original | Valor atual | Valor na base de dados |
---|---|---|---|
CustID | 101 | 101 | 101 |
LastName | Smith | Smith | Smith |
FirstName | Fernando | Bruno | Fernando |
A atualização é bem-sucedida porque os valores no banco de dados no momento da atualização correspondem aos valores originais que User2 tem.
Às 13h05, User1 muda o primeiro nome de "Bob" para "James" e tenta atualizar a linha.
Nome da coluna | Valor original | Valor atual | Valor na base de dados |
---|---|---|---|
CustID | 101 | 101 | 101 |
LastName | Smith | Smith | Smith |
FirstName | Fernando | Tiago | Bruno |
Neste ponto, User1 encontra uma violação de simultaneidade otimista porque o valor no banco de dados ("Robert") não corresponde mais ao valor original que User1 esperava ("Bob"). A violação de simultaneidade simplesmente informa que a atualização falhou. Agora precisa ser tomada a decisão de substituir as alterações fornecidas pelo Usuário2 pelas alterações fornecidas pelo Usuário1 ou cancelar as alterações pelo Usuário1.
Teste para violações de simultaneidade otimistas
Existem várias técnicas para testar uma violação de simultaneidade otimista. Uma envolve a inclusão de uma coluna de carimbo de data/hora na tabela. Os bancos de dados geralmente fornecem a funcionalidade de carimbo de data/hora que pode ser usada para identificar a data e a hora em que o registro foi atualizado pela última vez. Usando essa técnica, uma coluna de carimbo de data/hora é incluída na definição da tabela. Sempre que o registro é atualizado, o carimbo de data/hora é atualizado para refletir a data e hora atuais. Em um teste para violações de simultaneidade otimistas, a coluna de carimbo de data/hora é retornada com qualquer consulta do conteúdo da tabela. Quando uma atualização é tentada, o valor de carimbo de data/hora no banco de dados é comparado ao valor de carimbo de data/hora original contido na linha modificada. Se corresponderem, a atualização é executada e a coluna de carimbo de data/hora é atualizada com a hora atual para refletir a atualização. Se não corresponderem, ocorreu uma violação de simultaneidade otimista.
Outra técnica para testar uma violação de simultaneidade otimista é verificar se todos os valores de coluna originais em uma linha ainda correspondem aos encontrados no banco de dados. Por exemplo, considere a consulta seguinte:
SELECT Col1, Col2, Col3 FROM Table1
Para testar uma violação de simultaneidade otimista ao atualizar uma linha na Tabela 1, você emitirá a seguinte instrução UPDATE:
UPDATE Table1 Set Col1 = @NewCol1Value,
Set Col2 = @NewCol2Value,
Set Col3 = @NewCol3Value
WHERE Col1 = @OldCol1Value AND
Col2 = @OldCol2Value AND
Col3 = @OldCol3Value
Desde que os valores originais correspondam aos valores no banco de dados, a atualização é executada. Se um valor tiver sido modificado, a atualização não modificará a linha porque a cláusula WHERE não encontrará uma correspondência.
Observe que é recomendável sempre retornar um valor de chave primária exclusivo em sua consulta. Caso contrário, a instrução UPDATE anterior pode atualizar mais de uma linha, o que pode não ser sua intenção.
Se uma coluna na fonte de dados permitir nulos, talvez seja necessário estender a cláusula WHERE para verificar se há uma referência nula correspondente na tabela local e na fonte de dados. Por exemplo, a instrução UPDATE a seguir verifica se uma referência nula na linha local ainda corresponde a uma referência nula na fonte de dados ou se o valor na linha local ainda corresponde ao valor na fonte de dados.
UPDATE Table1 Set Col1 = @NewVal1
WHERE (@OldVal1 IS NULL AND Col1 IS NULL) OR Col1 = @OldVal1
Você também pode optar por aplicar critérios menos restritivos ao usar um modelo de simultaneidade otimista. Por exemplo, usar apenas as colunas de chave primária na cláusula WHERE faz com que os dados sejam substituídos, independentemente de as outras colunas terem sido atualizadas desde a última consulta. Você também pode aplicar uma cláusula WHERE somente a colunas específicas, resultando na substituição de dados, a menos que campos específicos tenham sido atualizados desde a última consulta.
O evento DataAdapter.RowUpdated
O evento RowUpdated do DataAdapter objeto pode ser usado em conjunto com as técnicas descritas anteriormente, para fornecer notificação ao seu aplicativo de violações de simultaneidade otimistas. RowUpdated ocorre após cada tentativa de atualizar uma linha Modified de um DataSet. Isso permite que você adicione código de manipulação especial, incluindo processamento quando ocorre uma exceção, adição de informações de erro personalizadas, adição de lógica de repetição e assim por diante. O RowUpdatedEventArgs objeto retorna uma propriedade RecordsAffected contendo o número de linhas afetadas por um comando de atualização específico para uma linha modificada em uma tabela. Ao definir o comando update para testar a simultaneidade otimista, a propriedade RecordsAffected retornará, como resultado, um valor de 0 quando ocorrer uma violação de simultaneidade otimista, porque nenhum registro foi atualizado. Se este for o caso, uma exceção é lançada. O evento RowUpdated permite que você manipule essa ocorrência e evite a exceção definindo um valor RowUpdatedEventArgs.Status apropriado, como UpdateStatus.SkipCurrentRow. Para obter mais informações sobre o evento RowUpdated , consulte Manipulando eventos DataAdapter.
Opcionalmente, você pode definir DataAdapter.ContinueUpdateOnError como true, antes de chamar Update, e responder às informações de erro armazenadas na propriedade RowError de uma linha específica quando a atualização for concluída. Para obter mais informações, consulte Informações de erro de linha.
Exemplo de simultaneidade otimista
A seguir está um exemplo simples que define o UpdateCommand de um DataAdapter para testar a simultaneidade otimista e, em seguida, usa o evento RowUpdated para testar violações de simultaneidade otimistas. Quando uma violação de simultaneidade otimista é encontrada, o aplicativo define o RowError da linha para a qual a atualização foi emitida para refletir uma violação de simultaneidade otimista.
Observe que os valores de parâmetro passados para a cláusula WHERE do comando UPDATE são mapeados para os valores originais de suas respetivas colunas.
' 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;
}
}