Administrar los conflictos de simultaneidad

Nota

En esta página se documenta cómo funciona la simultaneidad en EF Core y cómo administrar los conflictos de simultaneidad en la aplicación. Consulte Concurrency Tokens (Tokens de simultaneidad) para detalles sobre cómo configurar los tokens de simultaneidad en el modelo.

Sugerencia

Puede ver un ejemplo de este artículo en GitHub.

La simultaneidad de base de datos se refiere a las situaciones en las que varios procesos o usuarios acceden o cambian los mismos datos de una base de datos al mismo tiempo. El control de simultaneidad se refiere a los mecanismos específicos que se usan para garantizar la coherencia de los datos en presencia de cambios simultáneos.

EF Core implementa el control de simultaneidad optimista, lo que significa que permitirá que varios procesos o usuarios hagan cambios de manera independiente sin la sobrecarga que implica la sincronización o el bloqueo. En la situación ideal, estos cambios no interferirán entre sí y, por tanto, se realizarán correctamente. En el peor escenario, dos o más procesos intentarán hacer cambios conflictivos y solo uno de ellos se completará correctamente.

Funcionamiento del control de simultaneidad en EF Core

Las propiedades configuradas como tokens de simultaneidad se usan para implementar el control de simultaneidad optimista: cada vez que se realiza una operación de actualización o eliminación durante SaveChanges, el valor del token de simultaneidad en la base de datos se compara con el valor original leído por EF Core.

  • Si los valores coinciden, la operación se puede completar.
  • Si no coinciden, EF Core supone que otro usuario realizó una operación en conflicto y anula la transacción actual.

La situación en que otro usuario realizó una operación que entra en conflicto con la operación actual se conoce como conflicto de simultaneidad.

Los proveedores de base de datos son responsable de implementar la comparación de los valores de los tokens de simultaneidad.

En las bases de datos relacionales, EF Core incluye una comprobación del valor del token de simultaneidad en la cláusula WHERE de cualquier instrucción UPDATE o DELETE. Después de ejecutar las instrucciones, EF Core lee el número de filas que se vieron afectadas.

Si no se afectó ninguna fila, se detecta un conflicto de simultaneidad y EF Core genera la excepción DbUpdateConcurrencyException.

Por ejemplo, queremos configurar LastName en Person como token de simultaneidad. Luego, toda operación de actualización en Person incluirá la comprobación de la simultaneidad en la cláusula WHERE:

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

Resolución de los conflictos de simultaneidad

Siguiendo con el ejemplo anterior, si un usuario intenta guardar algunos cambios en una Person pero otro usuario ya cambió LastName, se generará una excepción.

En este punto, la aplicación simplemente podría informar al usuario que la actualización no se realizó correctamente debido a cambios en conflicto y siguió adelante. Pero podría ser conveniente pedirle al usuario que se asegure de que este registro sigue representando a la misma persona real y que reintente la operación.

Este proceso es un ejemplo de cómo resolver un conflicto de simultaneidad.

Resolver un conflicto de simultaneidad implica combinar los cambios pendientes del DbContext actual con los valores de la base de datos. Los valores que se van a combinar variarán en función de la aplicación y los puede dirigir el mismo usuario.

Existen tres conjuntos de valores disponibles para ayudar a resolver un conflicto de simultaneidad:

  • Los valores actuales son los valores que la aplicación intentó escribir en la base de datos.
  • Los valores originales son los valores que se recuperaron originalmente de la base de datos, antes de realizar cualquier edición.
  • Los valores de base de datos son los valores actualmente almacenados en la base de datos.

El enfoque general para controlar un conflicto de simultaneidad es:

  1. Detecte DbUpdateConcurrencyException durante SaveChanges.
  2. Use DbUpdateConcurrencyException.Entries para preparar un nuevo conjunto de cambios para las entidades afectadas.
  3. Actualice los valores originales del token de simultaneidad para reflejar los valores actuales en la base de datos.
  4. Reintente el proceso hasta que no haya ningún conflicto.

En el ejemplo siguiente, Person.FirstName y Person.LastName están configurados como tokens de simultaneidad. Hay un comentario // TODO: en la ubicación donde se incluye la lógica específica de la aplicación para elegir el valor que se guardará.

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.ExecuteSqlRaw(
    "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);
            }
        }
    }
}

Recursos adicionales

Vea Detección de conflictos en EF Core para obtener un ejemplo de ASP.NET Core con detección de conflictos.