Innanzitutto , dovresti sempre avere una corretta gestione delle transazioni in tutte le tue procedure in modo che non abbia importanza se vengono chiamate dal codice dell'app, da un'altra procedura, individualmente in una query ad hoc, da un processo di SQL Agent o in altro modo . Ma singole istruzioni DML, o codice che non apporta alcuna modifica, non necessitano di una Transazione esplicita. Quindi, ciò che sto raccomandando è:
- Avere sempre la struttura TRY / CATCH in modo che gli errori possano essere correttamente bollati
- Facoltativamente, includere le 3 parti di gestione delle Transazioni nel codice seguente se si hanno più istruzioni DML (poiché una singola istruzione è una transazione in sé). TUTTAVIA, al di fuori dell'aggiunta di qualche codice aggiuntivo laddove non sia specificamente necessario, se si preferisce avere un modello coerente, non fa male mantenere i 3 blocchi IF relativi alle Transazioni. In tal caso, consiglierei comunque di non conservare i 3 blocchi IF relativi alla transazione per i processi di sola selezione (ovvero di sola lettura).
Quando fai 2 o più istruzioni DML, tu necessario utilizzare qualcosa in linea con quanto segue (che può essere fatto anche per singole operazioni DML se si preferisce essere coerenti):
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;
Quando si esegue solo 1 istruzione DML o solo un SELECT, è possibile cavarsela con le seguenti opzioni:
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;
Secondo , è necessario gestire la transazione a livello di app solo se è necessario eseguire più di 1 query / stored procedure e tutte devono essere raggruppate in un'operazione atomica. Fare un singolo SqlCommand.Execute___
deve essere solo in un tentativo / cattura, ma non in una Transazione.
Ma fa male fare una Transazione a livello di app quando si effettua una sola chiamata? Se richiede MSDTC (Microsoft Distributed Transaction Coordinator), è un po 'più pesante sul sistema farlo a livello di app quando non è espressamente necessario. Personalmente, preferisco evitare le transazioni basate sul livello dell'app a meno che non sia assolutamente necessario in quanto riducono il potenziale delle transazioni orfane (se qualcosa è andato storto con il codice dell'app prima di eseguire il commit o il rollback). Ho anche scoperto che a volte rende il debug di alcune situazioni un po 'più difficile. Detto questo, non vedo nulla di tecnicamente sbagliato nel gestire anche la transazione a livello di app quando si effettua un singolo procchiamata; ancora una volta, una singola istruzione DML è una transazione propria e non richiede alcuna gestione esplicita delle transazioni su entrambi i livelli.