Primero , siempre debe tener un manejo adecuado de las transacciones en todos sus procedimientos para que no importe si son llamados por el código de la aplicación, por otro procedimiento, individualmente en una consulta ad-hoc, por un trabajo de Agente SQL u otro medio . Pero las declaraciones DML individuales, o el código que no realiza ninguna modificación, no necesita una transacción explícita. Entonces, lo que recomiendo es:
- Siempre tenga la estructura TRY / CATCH para que los errores puedan aparecer correctamente
- Opcionalmente, incluya las 3 piezas de manejo de transacciones en el código a continuación si tiene varias declaraciones DML (ya que una sola declaración es una transacción en sí misma). SIN EMBARGO, además de agregar algún código adicional donde no se necesita específicamente, si uno prefiere tener una plantilla consistente, entonces no está de más mantener los 3 bloques IF relacionados con la transacción. Pero en ese caso, aún recomendaría no mantener los 3 bloques IF relacionados con la transacción para procesos de solo SELECCIÓN (es decir, solo lectura).
Al hacer 2 o más instrucciones DML, debe usar algo similar a lo siguiente (que también se puede hacer para operaciones DML individuales si se prefiere ser coherente):
CREATE PROCEDURE [SchemaName].[ProcedureName]
(
@Param DataType
...
)
AS
SET NOCOUNT ON;
DECLARE @InNestedTransaction BIT;
BEGIN TRY
IF (@@TRANCOUNT = 0)
BEGIN
SET @InNestedTransaction = 0;
BEGIN TRAN; -- only start a transaction if not already in one
END;
ELSE
BEGIN
SET @InNestedTransaction = 1;
END;
-- { 2 or more DML statements (i.e. INSERT / UPDATE / DELETE) }
IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
BEGIN
COMMIT;
END;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(),
@ErrorState INT = ERROR_STATE(),
@ErrorSeverity INT = ERROR_SEVERITY();
-- optionally concatenate ERROR_NUMBER() and/or ERROR_LINE() into @ErrorMessage
RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);
RETURN;
END CATCH;
Al hacer solo 1 instrucción DML o solo un SELECT, puede salirse con la suya:
CREATE PROCEDURE [SchemaName].[ProcedureName]
(
@Param DataType
...
)
AS
SET NOCOUNT ON;
BEGIN TRY
-- { 0 or 1 DML statements (i.e. INSERT / UPDATE / DELETE) }
END TRY
BEGIN CATCH
DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(),
@ErrorState INT = ERROR_STATE(),
@ErrorSeverity INT = ERROR_SEVERITY();
-- optionally concatenate ERROR_NUMBER() and/or ERROR_LINE() into @ErrorMessage
RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);
RETURN;
END CATCH;
En segundo lugar , debe manejar la transacción en la capa de aplicación solo si necesita ejecutar más de 1 consulta / procedimiento almacenado y todos deben agruparse en una operación atómica. Hacer un solo SqlCommand.Execute___
solo tiene que estar en un intento / captura, pero no en una transacción.
Pero, ¿ duele hacer una transacción en la capa de la aplicación cuando solo hace una sola llamada? Si requiere MSDTC (Coordinador de transacciones distribuidas de Microsoft), entonces es un poco más pesado en el sistema hacer esto en la capa de la aplicación cuando no se necesita expresamente. Personalmente, prefiero evitar las transacciones basadas en la capa de la aplicación, a menos que sea absolutamente necesario, ya que reduce el potencial de transacciones huérfanas (si algo salió mal con el código de la aplicación antes de realizar el compromiso o la reversión). También he descubierto que a veces dificulta un poco la depuración de ciertas situaciones. Pero dicho esto, no veo nada técnicamente malo también maneja la transacción en la capa de aplicación al hacer un solo procllamada; de nuevo, una sola declaración DML es su propia transacción y no necesita ningún manejo de transacción explícito en ninguna de las capas.