Cómo manejar concurrencia con RowVersion

En una aplicación con cierto número de usuarios, puede ser habitual que se lleguen a producir errores de concurrencia. Si no quieres tener una perdida de información a causa de estos problemas, perjudicando la experiencia de tus usuarios, es necesarios que adoptes estrategias o mecanismos para controlar estos accesos concurrentes a la información. En esta entrada te voy a presentar una posibilidad con un buen nivel de automatismo, de forma que implementarla y controlar los errores sea rápido y fácil, dotándote de mayor eficiencia.

La estrategia que te presento el uso de la propiedad RowVersion a través de la Fluent Api de Entity Framework para un control de concurrencia denominado “pesimista”.

Si aún no te has topado con esta problemática, voy a comenzar contándote un caso típico de error de concurrencia.

Imagina que tienes el formulario con los datos F, Alice (A) abre el formulario y comienza a editar sus campos, pero antes de que persista sus datos, Bob (B) abre el mismo formulario con los datos iniciales F, realiza cambios y los persiste, quedado los datos almacenados como F’.

Para cuando Alice quiera guardar sus cambios, si no se controla la concurrencia, serán persistidos sobrescribiendo los datos F’ que Bob guardó, perdiéndose estos definitivamente.

Existen diferentes mecanismos para controlar estos cambios, voy a entrar a una de las aproximaciones basada en concurrencia pesimista como ya he mencionado. Esto consiste en forzar a Alice a actualizar los datos de su formulario, debiendo volver a modificarlo, viendo así los datos actualizados que Bob guardó, F’.

Una opción para manejar la persistencia con la aproximación pesimista, consiste en comparar qué versión del registro tienes en la base de datos, y confirmar si es la misma que se esta pretendiendo actualizar.

¿Cómo puedes gestionar esta versión de registro?

Todos los objetos sobre los que quieres controlar la concurrencia, deberán tener una propiedad para este fin, que identifique la versión del registro. Existen múltiples opciones, como por ejemplo rescatar de los datos de auditoria la fecha de ultima actualización, pero me voy a centrar en la que yo uso, RowVersion.

Cada objeto tendrá una propiedad de tipo byte[] que identificará esta RowVersion.

Cada vez que un objeto es enviado a cliente, deberá tener esta propiedad poblada con el valor contenido en la base de datos, de forma que cuando los datos del formulario se envíen de nuevo al servidor, tendrás este RowVersion disponible para poder compararlo, de nuevo, con el estado actual de la base de datos.

Si estas manejando la capa de datos a través de Entity Framework, usando su fluent Api en los mappings del modelo, se debe identificar esta propiedad RowVersion del objeto de la siguiente manera.

Mediante este mapeo, si estas funcionando por ejemplo sobre SQL, tu tabla tendrá una columna timespan, o deberás asegurarte de tenerla si no trabajas con code first haciendo uso de migrations como explicare en otro hilo que tengo pendiente.

La columna timespan será automáticamente poblada con cada insert o actualizada con cada update sin que tengas que hacer nada especial por controlarlo. Esto facilitará mucho tu tarea de controlar la concurrencia.

Cuando Entity Framework ejecuta una operación de persistencia de datos con esta propiedad, comprobará si el registro que se va a actualizar contiene un RowVersion coincidente con el del registro que se pretende persistir.

Si el RowVersion no coincide, ocurrirá una excepción de tipo DbUpdateConcurrencyException.

De cómo manejes esa excepción, llevando a cabo unas u otras operaciones, dependerá que estés usando una concurrencia pesimista u optimista.

Llegado ese caso significará que un segundo usuario ha actualizado el registro y por tanto el RowVersion fue actualizado respecto al que se esta utilizando para la actualización que se esta comprobando, habiendo quedado obsoletos los datos que se manipulaban.

Pero… ¿de verdad vas a realizar toda una serie de operaciones de lógica de negocio, para terminar con una posible excepción de concurrencia?

Quizá prefieras realizar la comparación para el control de concurrencia de forma manual al inicio de las operaciones y devolver una excepción si se detecta el error de concurrencia, interrumpiendo el flujo de la lógica de negocio y ahorrando por tanto operaciones innecesarias.

Para ello en cuanto recuperas de la base de datos el objeto a ser actualizado, compara su RowVersion con el objeto actualizado que has recibido.

Y listo, según el resultado bool podrás determinar si ha ocurrido un problema de concurrencia, manejando la situación como prefieras y evitando que el flujo continúe con operaciones que no te llevarán a ningún sitio, ya que la operación de persistencia devolverá la excepción DbUpdateConcurrencyException.

Conclusiones

El timespan de la base de datos se actualizará solito, gracias a IsRowVersion(), y luego tu podrás verificar la coincidencia de RowVersion al comienzo de cada operación de update de forma manual, o mediante una excepción al ejecutar el savechange como ultima medida de seguridad.

¿Preferirías usar un mecanismo de persistencia optimista? ¿Lo ves mucho más útil en determinadas situaciones? Comentalo 😉

Deja un comentario