Tengo una API que realiza transacciones utilizando diferentes proveedores y para cada proveedor, manejo un contador interno en una tabla. El problema surge porque recibiremos más tráfico y tenemos una transacción con numeración repetida.
La consulta que uso para el contador es la siguiente:
public async Task<int> GetNumeration(int id)
{
var providerNumerationDb = await _dbContextEf
.providerNumeration
.SingleOrDefaultAsync(x => x.providerId == id);
if (providerNumerationDb == null)
{
providerNumerationDb = new ProviderNumeration
{
providerId = id,
number = 1,
};
await _dbContextEf.AddAsync(providerNumerationDb);
}
else
{
providerNumerationDb.Number += 1;
}
await _dbContextEf.SaveChangesAsync(0);
_dbContextEf.Entry(providerNumerationDb).State = EntityState.Detached;
return providerNumerationDb.Number;
}
Quiero conocer la mejor estrategia para hacer un Rowlock sin consecuencias de rendimiento u otra solución. Si cree que la solución es mejor usando StoredProcedure, por ejemplo, o de otra manera, también es válida, podemos cambiar la implementación.
A continuación se muestra un ejemplo utilizando una transacción SERIALIZABLE
. Se adquiere un bloqueo de fila en la clave actualizada para la actualización y, cuando la ID aún no existe, se adquiere un bloqueo de rango de clave para la inserción. Este último limitará la concurrencia para otras inserciones dentro del rango, pero probablemente no será una preocupación ya que la transacción se confirmará de inmediato, liberando los bloqueos.
CREATE TABLE dbo.Provider(
ProviderId int NOT NULL CONSTRAINT PK_Provider PRIMARY KEY
, Number int NOT NULL
);
GO
CREATE PROCEDURE dbo.GetNextProviderNumber
@ProviderID int
AS
SET NOCOUNT, XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
DECLARE @Number int;
BEGIN TRY
BEGIN TRAN;
UPDATE dbo.Provider
SET @Number = Number += 1
WHERE ProviderID = @ProviderID;
IF @@ROWCOUNT = 0
BEGIN
SET @Number = 1;
INSERT INTO dbo.Provider(ProviderID, Number)
VALUES(@ProviderID, @Number);
END
COMMIT;
SELECT @Number AS Number;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK;
THROW;
END CATCH;
GO