我们是否需要在 C# 代码和存储过程中处理事务

Rak*_*aur 17 sql-server ado.net t-sql transaction c#

我们真的需要 C# 中的事务处理以及双方的数据库存储过程吗?

C#:

Using(transaction with transaction scope)
{
     Execute stored proc;
     Transaction. Complete;
}
Run Code Online (Sandbox Code Playgroud)

SQL存储过程:

Create process
As
Begin try
    Begin transaction
    Commit
End try
Begin catch
    Rollback
End catch
Run Code Online (Sandbox Code Playgroud)

Sol*_*zky 25

首先,您应该始终在所有过程中进行适当的事务处理,以便它们是由应用程序代码、另一个过程、在临时查询中单独调用、由 SQL 代理作业或其他方式调用都无关紧要. 但是单个 DML 语句或不进行任何修改的代码不需要显式事务。所以,我推荐的是:

  • 始终具有 TRY / CATCH 结构,以便可以正确冒泡错误
  • 如果您有多个 DML 语句(因为单个语句本身就是一个事务),则可以选择在下面的代码中包含 3 个事务处理部分。然而,除了在不需要的地方添加一些额外的代码之外,如果一个人更喜欢有一个一致的模板,那么保留在 3 个与事务相关的 IF 块中也没有什么坏处。但在那种情况下,我仍然建议不要为 SELECT-only(即只读)过程保留 3 个与事务相关的 IF 块。

在执行 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;
Run Code Online (Sandbox Code Playgroud)

当仅执行 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;
Run Code Online (Sandbox Code Playgroud)

其次只有当您需要执行 1 个以上的查询/存储过程并且它们都需要分组为一个原子操作时,才应该在应用层处理事务。做单SqlCommand.Execute___只需要在 try / catch 中,而不是在 Transaction 中。

但是,当只进行一次调用时,在应用层执行事务会不会有什么坏处?如果它需要 MSDTC(Microsoft 分布式事务协调器),那么在没有明确需要的情况下,在应用层执行此操作对系统来说有点重。就个人而言,除非绝对必要,否则我更喜欢避免基于应用程序层的事务,因为它减少了孤立事务的可能性(如果在执行提交或回滚之前应用程序代码出现问题)。我还发现它有时会使调试某些情况更加困难。但话虽如此,我认为在制作单个proc也在应用层处理事务在技术上没有任何问题称呼; 同样,单个 DML 语句是它自己的事务,不需要任何层的任何显式事务处理。