Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Num ambiente multiutilizador, existem dois modelos para atualizar dados numa base de dados: concorrência otimista e concorrência 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 concorrência pessimista envolve o bloqueio de linhas na fonte de dados para impedir que outros utilizadores modifiquem os dados de uma maneira que afete o utilizador 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.
Observação
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 disputa por 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 seguintes apresentam um exemplo de concorrência 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 |
|---|---|---|---|
| ID de Cliente | 101 | 101 | 101 |
| Apelido | Silva | Silva | Silva |
| Primeiro nome | Joaquim | Joaquim | Joaquim |
Às 13h01, User2 lê a mesma linha.
Às 13h03, o Utilizador 2 muda FirstName de "Bob" para "Robert" e atualiza a base de dados.
| Nome da coluna | Valor original | Valor atual | Valor na base de dados |
|---|---|---|---|
| ID de Cliente | 101 | 101 | 101 |
| Apelido | Silva | Silva | Silva |
| Primeiro nome | Joaquim | Roberta | Joaquim |
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 |
|---|---|---|---|
| ID de Cliente | 101 | 101 | 101 |
| Apelido | Silva | Silva | Silva |
| Primeiro nome | Joaquim | Tiago | Roberta |
Neste ponto, o Utilizador1 encontra uma violação de concorrência otimista porque o valor no banco de dados ("Robert") já não corresponde ao valor original que o Utilizador1 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.
Testagem de violações de concorrência otimista
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 esta técnica, uma coluna de timestamp é 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. Num teste para violações de concorrência otimista, a coluna de carimbo de data/hora é retornada com qualquer consulta ao 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 é realizada e a coluna de marca temporal é atualizada com a hora atual, refletindo 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 originais das colunas numa linha ainda correspondem aos que foram 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 objeto DataAdapter pode ser usado em conjunto com as técnicas descritas anteriormente para notificar a aplicação sobre violações de concorrência otimista.
RowUpdated ocorre após cada tentativa de atualizar uma Modified linha a partir 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 devolve uma RecordsAffected propriedade contendo o número de linhas afetadas por um determinado comando de atualização para uma linha modificada numa tabela. Ao definir o comando update para testar a concorrência otimista, a RecordsAffected propriedade irá, como resultado, devolver um valor 0 quando ocorrer uma violação de concorrência otimista, porque nenhum registo foi atualizado. Se for esse o caso, uma exceção é lançada. O RowUpdated evento permite-lhe lidar com esta ocorrência e evitar a exceção ao definir um valor RowUpdatedEventArgs.Status apropriado, como UpdateStatus.SkipCurrentRow. Para mais informações sobre o evento RowUpdated, consulte Processamento de Eventos do DataAdapter.
Opcionalmente, pode definir DataAdapter.ContinueUpdateOnError como true, antes de chamar Update, e responder aos detalhes do erro armazenados na RowError propriedade de uma determinada linha quando o Update estiver concluído. Para obter mais informações, consulte Informações de erro de linha.
Exemplo de concorrência otimista
Segue-se um exemplo simples que define o UpdateCommand de a DataAdapter para testar a concorrência otimista, e depois usa o RowUpdated evento para testar violações de concorrência otimista. Quando é encontrada uma violação de concorrência otimista, a aplicação define o RowError da linha para a qual a atualização foi emitida para refletir uma violação de concorrência otimista.
Note que os valores dos parâmetros passados para a cláusula WHERE do comando UPDATE são mapeados para os Original valores das 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;
}
}