Во-первых , у вас всегда должна быть правильная обработка транзакций во всех ваших процедурах, чтобы не имело значения, если они вызываются кодом приложения, другой процедурой, индивидуально в специальном запросе, заданием агента SQL или каким-либо другим способом. , Но отдельные операторы DML или код, который не вносит никаких изменений, не требуют явной транзакции. Итак, что я рекомендую это:
- Всегда используйте структуру TRY / CATCH, чтобы ошибки можно было правильно всплыть
- При желании включите 3 фрагмента обработки транзакций в код ниже, если у вас есть несколько операторов DML (поскольку один оператор сам по себе является транзакцией). ОДНАКО, кроме добавления некоторого дополнительного кода, где он специально не нужен, если кто-то предпочитает иметь непротиворечивый шаблон, тогда не помешает оставаться в 3 блоках IF, связанных с транзакциями. Но в этом случае я бы по-прежнему рекомендовал не хранить 3 блока IF, относящихся к транзакциям, для только SELECT (т.е. только для чтения) процедур.
При выполнении 2 или более операторов DML необходимо использовать что-то вроде следующего (что также можно сделать для отдельных операций DML, если вы предпочитаете быть последовательными):
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;
Выполняя всего 1 оператор DML или просто SELECT, вы можете избежать следующих действий:
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;
Во-вторых , вам следует обрабатывать транзакцию на уровне приложения только в том случае, если вам нужно выполнить более 1 запроса / хранимой процедуры, и все они должны быть сгруппированы в элементарную операцию. Выполнение сингла SqlCommand.Execute___
должно быть только в попытке / улове, но не в транзакции.
Но вредно ли выполнять транзакцию на уровне приложения, когда выполняется только один вызов? Если для этого требуется MSDTC (координатор распределенных транзакций Microsoft), то в системе немного сложнее сделать это на уровне приложения, если в этом нет явной необходимости. Лично я предпочитаю избегать транзакций на уровне приложения, за исключением случаев, когда это абсолютно необходимо, поскольку это снижает вероятность потерянных транзакций (если что-то пошло не так с кодом приложения перед выполнением фиксации или отката). Я также обнаружил, что это иногда затрудняет отладку определенных ситуаций. Но это , как говорится, я не вижу ничего технически неправильно с также обработки транзакции в приложении слоя при создании единого проквызов; опять же, один оператор DML является собственной транзакцией и не требует какой-либо явной обработки транзакций на любом уровне.