Transactions - lobodava/artisan-orm GitHub Wiki

For the cases when it's necessary to lift up a transaction from database to application level, Artisan.Orm provides the RepositoryBase method BeginTransaction:

public void BeginTransaction(Action<SqlTransaction> action)

public void BeginTransaction(IsolationLevel isolationLevel, Action<SqlTransaction> action)

This method:

  • opens the connection, that is created in RepositoryBase constructor;
  • begins SqlTransaction and passes it as an argument to Action<SqlTransaction> parameter;
  • begins try catch finaly block:
    • in try part runs Action<SqlTransaction> parameter,
    • in catch part rollbacks the transaction,
    • in final part closes the connection and disposes the transaction.

If the repository class of the executing method is inherited from the RepositoryBase, then GetByCommand and ExecuteCommand methods bind the commands to the started transaction.

Example:

public void CheckRuleForUser(Int32 userId)
{
    // begin transaction with RepeatableRead isolation level
  
    BeginTransaction(IsolationLevel.RepeatableRead, tran =>
    {
        // read user by userId

        var user = GetByCommand(cmd =>
        {
            cmd.UseProcedure("dbo.GetUserById");
            cmd.AddIntParam("@Id", userId);

            return cmd.ReadTo<User>();
        });

        // change the user in accordance with some rules

        if (Array.IndexOf(user.RoleIds, 2) > -1 && Array.IndexOf(user.RoleIds, 3) == -1)
        {
            var newRoleIds = user.RoleIds.ToList();
            newRoleIds.Add(3);
            user.RoleIds = newRoleIds.ToArray();
        }
             
        // save the user
   
        ExecuteCommand(cmd =>
        {
            cmd.UseProcedure("dbo.SaveUser");

            cmd.AddTableRowParam("@User", user);
            cmd.AddTableParam("@RoleIds", user.RoleIds);
        });

        // commit transaction
        
        tran.Commit();
    });
}   

If to omit tran.Commit(); at the end, then the transaction will be rolled back - this can be used for testing of methods which change the data.

Transaction in Stored Procedures

In order to allow an application transaction, a stored procedure should have a check for the outer transaction.

For example:

create procedure dbo.SaveUser
    ...
as
begin
    
    declare @StartTranCount int;

    begin try
        set @StartTranCount = @@trancount;

        -- Begin transaction only when there is no outer transaction

        if @StartTranCount = 0 begin transaction;


        begin -- save Users 
            ...
        end; 


        begin -- save UserRoles
            ...
        end;

        -- Commit transaction only when there is no outer transaction

        if @StartTranCount = 0 commit transaction;

    end try
    begin catch

        -- Rollback transaction only when there is no outer transaction

        if xact_state() <> 0 and @StartTranCount = 0 rollback transaction;
        
        declare @ErrorMessage nvarchar(4000) = dbo.GetErrorMessage();
        raiserror (@ErrorMessage, 16, 1);
        return;
    end catch;

end;

See also:

⚠️ **GitHub.com Fallback** ⚠️