Su preocupación es válida: en el sitio web de uso frecuente es más probable que suceda y la solución no es muy fácil. Puede usar el lado del cliente Guid
como lo describe @Mikecito pero tiene un rendimiento significativo y creo que no desea usarlo.
La forma en que está haciendo esto en este momento es muy mala porque la única solución es envolver su código en una transacción serializable única: la transacción debe contener tanto seleccionar Id como guardar el registro. Esto hará que el acceso a su InventoryObjects
sea secuencial porque cada select max bloqueará toda la tabla hasta que se haya confirmado la transacción; nadie más podrá leer o escribir datos en la tabla durante la transacción de inserción. No tiene por qué ser un problema en un sitio poco visitado, pero puede ser NO IR en un sitio visitado con frecuencia. No hay forma de hacerlo de manera diferente en su configuración actual.
La mejora parcial consiste en utilizar una tabla separada para mantener el valor máximo + el procedimiento almacenado para obtener el siguiente valor e incrementar el valor almacenado en la operación atómica (en realidad simula secuencias de Oracle). Ahora la única complicación es si necesita la secuencia sin espacios. Por ejemplo, si algo sale mal con el guardado de InventoryObject
, el ID seleccionado se perderá y creará un espacio en la secuencia del identificador. Si necesita secuencia sin huecos, debe volver a usar la transacción para obtener el siguiente Id y guardar el registro, pero esta vez solo bloqueará un solo registro en la tabla de secuencia. Recuperar el Id de la tabla de secuencia debe ser lo más cercano posible a guardar los cambios para minimizar el tiempo cuando la grabación de la secuencia está bloqueada.
Aquí es muestra de la tabla de secuencia y el procedimiento para la secuencia de servidor SQL:
CREATE TABLE [dbo].[Sequences]
(
[SequenceType] VARCHAR(20) NOT NULL, /* Support for multiple sequences */
[Value] INT NOT NULL
)
CREATE PROCEDURE [dbo].[GetNextSequenceValue]
@SequenceType VARCHAR(20)
AS
BEGIN
DECLARE @Result INT
UPDATE [dbo].[Sequences] WITH (ROWLOCK, UPDLOCK)
SET @Result = Value = Value + 1
WHERE SequenceType = @SequenceType
RETURN @Result
END
La tabla no necesita ser mapeada por código primero - que nunca acceder a él directamente. Debe crear un inicializador de base de datos personalizado para agregar la tabla y el procedimiento almacenado para usted cuando EF crea una base de datos. Puede intentar un enfoque similar al described here. También debe agregar un registro de inicialización para su secuencia con el valor inicial.
Ahora sólo tiene que llamar a procedimiento almacenado para obtener un valor antes de que se va a guardar el registro:
// Prepare and insert record here
// Transaction is needed only if you don't want gaps
// This whole can be actually moved to overriden SaveChanges in your context
using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
record.Id = context.Database.ExecuteStoreCommand("dbo.GetNextSequenceValue @SequenceType",
new SqlParameter("SequenceType", "InventoryObjects"));
context.SaveChanges();
}
¿Por qué no tiene que usar la identidad? –