Como tratar conflitos de simultaneidadeHandling Concurrency Conflicts

Observação

Esta página documenta como a simultaneidade funciona no EF Core e como lidar com conflitos de simultaneidade no seu aplicativo.This page documents how concurrency works in EF Core and how to handle concurrency conflicts in your application. Confira Tokens de Simultaneidade para obter detalhes sobre como configurar os tokens de simultaneidade no seu modelo.See Concurrency Tokens for details on how to configure concurrency tokens in your model.

Dica

Veja o exemplo deste artigo no GitHub.You can view this article's sample on GitHub.

Simultaneidade do banco de dados se refere a situações nas quais vários processos ou usuários acessam ou alteram os mesmos dados em um banco de dados ao mesmo tempo.Database concurrency refers to situations in which multiple processes or users access or change the same data in a database at the same time. Controle de simultaneidade se refere a mecanismos específicos usados para garantir a consistência dos dados na presença de alterações simultâneas.Concurrency control refers to specific mechanisms used to ensure data consistency in presence of concurrent changes.

O EF Core implementa o controle de simultaneidade otimista, o que significa que ele permitirá que vários processos ou usuários façam alterações de forma independente, sem a sobrecarga da sincronização ou bloqueio.EF Core implements optimistic concurrency control, meaning that it will let multiple processes or users make changes independently without the overhead of synchronization or locking. Em uma situação ideal, essas alterações não irão interferir umas nas outras e elas poderão ser bem-sucedidas.In the ideal situation, these changes will not interfere with each other and therefore will be able to succeed. Na pior das hipóteses, dois ou mais processos tentarão fazer alterações conflitantes e apenas uma delas terá êxito.In the worst case scenario, two or more processes will attempt to make conflicting changes, and only one of them should succeed.

Como funciona o controle de simultaneidade no EF CoreHow concurrency control works in EF Core

As propriedades configuradas como tokens de simultaneidade são usadas para implementar um controle de simultaneidade otimista: sempre que uma operação de atualização ou exclusão é realizada durante SaveChanges, o valor do token de simultaneidade no banco de dados é comparado ao valor original lido pelo EF Core.Properties configured as concurrency tokens are used to implement optimistic concurrency control: whenever an update or delete operation is performed during SaveChanges, the value of the concurrency token on the database is compared against the original value read by EF Core.

  • Se os valores coincidirem, a operação pode ser concluída.If the values match, the operation can complete.
  • Se os valores não coincidirem, o EF Core presume que outro usuário executou uma operação conflitante e anula a transação atual.If the values do not match, EF Core assumes that another user has performed a conflicting operation and aborts the current transaction.

A situação quando outro usuário executou uma operação que conflita com a operação atual é conhecida como conflito de simultaneidade.The situation when another user has performed an operation that conflicts with the current operation is known as concurrency conflict.

Os provedores do banco de dados são responsáveis por implementar a comparação dos valores do token de simultaneidade.Database providers are responsible for implementing the comparison of concurrency token values.

Em bancos de dados relacionais, o EF Core inclui uma verificação do valor do token de simultaneidade na cláusula WHERE de qualquer instrução UPDATE ou DELETE.On relational databases EF Core includes a check for the value of the concurrency token in the WHERE clause of any UPDATE or DELETE statements. Depois de executar as instruções, o EF Core lê o número de linhas que foram afetadas.After executing the statements, EF Core reads the number of rows that were affected.

Se nenhuma linha for afetada, um conflito de simultaneidade será detectado e o EF Core gera DbUpdateConcurrencyException.If no rows are affected, a concurrency conflict is detected, and EF Core throws DbUpdateConcurrencyException.

Por exemplo, é aconselhável configurar LastName em Person como um token de simultaneidade.For example, we may want to configure LastName on Person to be a concurrency token. Em seguida, qualquer operação de atualização em Pessoa incluirá a verificação de simultaneidade na cláusula WHERE:Then any update operation on Person will include the concurrency check in the WHERE clause:

UPDATE [Person] SET [FirstName] = @p1
WHERE [PersonId] = @p0 AND [LastName] = @p2;

Como resolver conflitos de simultaneidadeResolving concurrency conflicts

Continuando com o exemplo anterior, se um usuário tentar salvar algumas alterações em um Person, mas outro usuário já tiver alterado o LastName, uma exceção será gerada.Continuing with the previous example, if one user tries to save some changes to a Person, but another user has already changed the LastName, then an exception will be thrown.

Neste ponto, o aplicativo pode simplesmente informar ao usuário que a atualização não teve êxito devido a alterações conflitantes e avançar.At this point, the application could simply inform the user that the update was not successful due to conflicting changes and move on. Mas, talvez seja desejável consultar o usuário para garantir que este registro represente a mesma pessoa e repetir a operação.But it may be desirable to prompt the user to ensure this record still represents the same actual person and to retry the operation.

Este processo é um exemplo de como resolver um conflito de simultaneidade.This process is an example of resolving a concurrency conflict.

Resolver um conflito de simultaneidade envolve mesclar as alterações pendentes a partir do DbContext atual com os valores no banco de dados.Resolving a concurrency conflict involves merging the pending changes from the current DbContext with the values in the database. Os valores que serão mesclados irão variar com base no aplicativo e poderão ser direcionados pela entrada do usuário.What values get merged will vary based on the application and may be directed by user input.

Há três conjuntos de valores disponíveis para ajudar a resolver um conflito de simultaneidade:There are three sets of values available to help resolve a concurrency conflict:

  • Valores atuais são os valores que o aplicativo estava tentando gravar no banco de dados.Current values are the values that the application was attempting to write to the database.

  • Valores originais são os valores que foram originalmente recuperados do banco de dados, antes de todas as edições feitas.Original values are the values that were originally retrieved from the database, before any edits were made.

  • Valores do banco de dados são os valores armazenados atualmente no banco de dados.Database values are the values currently stored in the database.

A abordagem geral para lidar com um conflito de simultaneidade é:The general approach to handle a concurrency conflicts is:

  1. Capturar DbUpdateConcurrencyException durante SaveChanges.Catch DbUpdateConcurrencyException during SaveChanges.
  2. Use DbUpdateConcurrencyException.Entries para preparar um novo conjunto de alterações para as entidades afetadas.Use DbUpdateConcurrencyException.Entries to prepare a new set of changes for the affected entities.
  3. Atualize os valores originais do token de simultaneidade para refletir os valores atuais no banco de dados.Refresh the original values of the concurrency token to reflect the current values in the database.
  4. Tente novamente o processo até o conflito ocorrer.Retry the process until no conflicts occur.

No exemplo a seguir, Person.FirstName e Person.LastName são configurados como tokens de simultaneidade.In the following example, Person.FirstName and Person.LastName are setup as concurrency tokens. Há um comentário // TODO: no local onde você inclui a lógica específica para o aplicativo para escolher o valor a ser salvo.There is a // TODO: comment in the location where you include application specific logic to choose the value to be saved.

using (var context = new PersonContext())
{
    // Fetch a person from database and change phone number
    var person = context.People.Single(p => p.PersonId == 1);
    person.PhoneNumber = "555-555-5555";

    // Change the person's name in the database to simulate a concurrency conflict
    context.Database.ExecuteSqlCommand(
        "UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1");

    var saved = false;
    while (!saved)
    {
        try
        {
            // Attempt to save changes to the database
            context.SaveChanges();
            saved = true;
        }
        catch (DbUpdateConcurrencyException ex)
        {
            foreach (var entry in ex.Entries)
            {
                if (entry.Entity is Person)
                {
                    var proposedValues = entry.CurrentValues;
                    var databaseValues = entry.GetDatabaseValues();

                    foreach (var property in proposedValues.Properties)
                    {
                        var proposedValue = proposedValues[property];
                        var databaseValue = databaseValues[property];

                        // TODO: decide which value should be written to database
                        // proposedValues[property] = <value to be saved>;
                    }

                    // Refresh original values to bypass next concurrency check
                    entry.OriginalValues.SetValues(databaseValues);
                }
                else
                {
                    throw new NotSupportedException(
                        "Don't know how to handle concurrency conflicts for "
                        + entry.Metadata.Name);
                }
            }
        }
    }
}