Bueno .., el tema de la concurrencia es siempre muy
problemático y difícil de tratar.
La aquitectura que estoy usando es desconectada, es decir, me
paso la mayor parte del tiempo desconectado de SQL Server, sólo me conecto para
cargar los datos o para actualizarlos y en seguida me desconecto. Con esta
arquitectura sólo tengo una opción: usar concurrencia optimista ya que la
pesimista requiere mantener una conexión abierta. El modelo de concurrencia
optimista tiene el inconveniente de que se pueden producir conflictos de
concurrencia, pero tiene la ventaja de la simultaneidad: los usuarios pueden
acceder simultáneamente a la información sin ningún problema ya que los bloqueos
que se producen son más livianos y de muy corta duración.
Un conflicto de concurrencia se produce cuando un usuario A,
al intentar guardar un registro R, ese registro R ha sido modificado o eliminado
por otro usuario B desde la última vez que lo leyó A. Los conflictos de
concurrencia hay que detectarlos y gestionarlos y existen varias alternativas
para ello. Por ejemplo, si quisiéramos detectar el conflicto de concurrencia que
se produce al actualizar un registro porque otro usuario lo ha modificado,
tendríamos que incluir en la clásula WHERE de la instrucción UPDATE los valores
que originalmente se leyeron, de esta manera si otro usuario ha modificado el
registro, la intrucción UPDATE no actualizaría ningún registro al no cumplir la
condición de WHERE y el número de registros afectados por la instrucción
UPDATE sería cero, lo que indicaría la presencia de un conflicto de
concurrencia, pero también sería posible que lo que haya ocurrido es que el
registro se haya eliminado. Para distinguir si el conflicto se ha producido por
modificación o por eliminación, tendríamos que intentar volver a leer el
registro.
El acceso a SQL Server lo hago únicamente por medio de
procedimientos almacenados, y son la forma de esos procedimientos almacenados lo
que determina como se detectan los conflictos de concurrencia.
Pongamos un ejemplo para aclarar ideas. Supongamos que tengo
esta tabla en SQL Server:
CREATE TABLE Empleados(
IdEmpleado int identity(1,1) PRIMARY KEY,
DNI varchar(10) NOT NULL UNIQUE,
Nombre varchar(50) NOT NULL
)
y este procedimiento almacenado para actualizar empleados:
CREATE PROCEDURE spActualizarEmpleado
@IdEmpleadoOriginal int,
@DNINuevo varchar(10),
@NombreNuevo varchar(50),
@DNIOriginal varchar(10),
@NombreOriginal varchar(50)
AS
UPDATE Empleados
SET DNI = @DNINuevo, Nombre = @NombreNuevo
WHERE IdEmpleado = @IdEmpleadoOriginal AND DNI =
@DNIOriginal AND Nombre = @NombreOriginal
SELECT * FROM Empleados WHERE IdEmpleado =
@IdEmpleadoOriginal
Los nuevos son los valores nuevos que han introducido los usuarios y los
originales son los que originalmente leyeron.
Supongamos que
el usuario A carga el empleado 1
el usuario B carga el empleado 1
el usuario B lo modifica y lo guarda
el usuario A lo modifica
Cuando el usuario A intenta guardar el registro, update no actualiza
ningún registro porque no se cumple la where, hay un conflicto de concurrencia,
pero la SELECT devuelve el registro tal y como lo guardó B produciendo un
refresco.
Esto es lo que hace la dll:
- Si el procedimiento almacenado no afecta a ningún registro considera que
hay un conflicto de concurrencia y entones:
- Si el procedimiento almacenado devuelve un recordset:
- Si ese recordset tiene un registro el conflicto ha sido por
modificación por parte de otro usuario y refresca el registro local
de Access.
- Si ese recordset no tiene ningún registro el conflicto ha sido por
eliminación por parte de otro usuario y lo elimina de la tabla
local
- Si el procedimiento almacenado no devuelve un recordset, no se puede
determinar si es por eliminación o por modificación y simplemente tira por
la calle de en medio: considera que se ha eliminado.
- Si el procedimiento almacenado afecta a un registro considera que no hay
conflicto de concurrecia y si el procedimiento almacenado devuelve un
recordset, coje el primer registro devuelto y lo usa para refrescar el
registro en la tabla local de Access.
- En caso de un conflicto de concurrencia o cualquier otro tipo de error, la
dll lanza un error en tiempo de ejecución y registra el error en una tabla
local de Access
Pero ha veces no nos interesa complicarnos tanto la vida, puede que no nos
interese detectar conflictos de concurrencia porque otro usuario haya modificado
el registro desde la última vez que lo leyó y
simplemente queremos forzar la actualización. Este método se conoce
como la técnica de que "el último que llega gana". El procedimiento almacenado
sería entonces más sencillo y eficiente:
CREATE PROCEDURE spActualizarEmpleado
@IdEmpleadoOriginal int,
@DNINuevo varchar(10),
@NombreNuevo varchar(50),
AS
UPDATE Empleados
SET DNI = @DNINuevo, Nombre = @NombreNuevo
WHERE IdEmpleado = @IdEmpleadoOriginal
Con esta técnica aún se pueden detectar conflictos por eliminación aunque
no por actualización.
La técnica de "el último que llega gana" no es mala, pero
tiene algunos inconvenientes. Entre otros, está el tema de la posibilidad
de pérdida de actualización. Por ejemplo:
- El usuario A carga empleado 1
- El usuario B carga empleado 1
- El usuario B cambia el nombre y lo guarda
- El usuario A cambia el DNI y lo guarda
En esta situación la modificación hecha por el usuario B se pierde,
reescribiéndose el nombre por lo que leyó A.