我有两个存储过程,我想在事务中执行包装。由于各种原因,我需要在我的应用程序代码中而不是在数据库中处理事务。
目前,我的代码如下所示:
try
{
using (SqlConnection conn = Connection())
{
conn.Open();
using (SqlTransaction sqlTrans = conn.BeginTransaction())
{
try
{
using (SqlCommand cmd1 = new SqlCommand("Stored_Proc_1", conn, sqlTrans))
{
cmd1.CommandType = CommandType.StoredProcedure;
cmd1.ExecuteNonQuery();
}
using (SqlCommand cmd2 = new SqlCommand("Stored_Proc_2", conn, sqlTrans))
{
cmd2.CommandType = CommandType.StoredProcedure;
cmd2.ExecuteNonQuery();
}
sqlTrans.Commit();
}
catch
{
sqlTrans.Rollback();
throw;
}
}
conn.Close();
}
}
catch (SqlException ex)
{
// exception handling and logging code here...
}
当其中一个存储过程引发错误时,我看到的异常消息如下:
Error message from raiserror within stored procedure.
Transaction count after EXECUTE indicates that a COMMIT or ROLLBACK TRANSACTION statement is missing. Previous count = 1, current count = 0.
这是有道理的,因为在第一次捕获时,事务尚未回滚。
但我想要一个“干净”错误(没有tran计数消息 - 我对此不感兴趣,因为我我回滚事务)我的异常处理代码。 有没有办法可以重构我的代码来实现这一目标?
修改
我存储过程的基本结构如下所示:
create proc Stored_Proc_1
as
set nocount on
begin try
begin transaction
raiserror('Error raised by Stored_Proc_1', 16, 1)
commit
end try
begin catch
if (@@trancount > 0) rollback
declare @ErrMsg nvarchar(4000), @ErrSeverity int, @ErrProc sysname, @ErrLine varchar(10)
select @ErrMsg = ERROR_MESSAGE(), @ErrSeverity = ERROR_SEVERITY(), @ErrProc = ERROR_PROCEDURE(), @ErrLine = ERROR_LINE()
-- log the error
-- sql logging code here...
raiserror(@ErrMsg, @ErrSeverity, 1)
end catch
更新 我已经从我的存储过程中进行了事务处理,这似乎已经解决了问题。显然我做错了 - 但我仍然想知道如何做对。从存储过程中删除事务是最佳解决方案吗?
答案 0 :(得分:5)
好吧,conn.Close()
无论如何都可能会被using
关闭(如果你想一想,奇怪的是我们在异常后只Close()
了它
您的任何一个存储过程是否在其自身内部执行任何事务代码(未回滚/已提交)?听起来 是问题所在......?如果有的话,错误消息告诉我,其中一个存储过程正在执行COMMIT
,即使它没有启动事务 - 可能是由于(不正确)方法:
-- pseduo-TSQL
IF @@TRANCOUNT = 0 BEGIN TRAN
-- ...
IF @@TRANCOUNT > 0 COMMIT TRAN -- or maybe = 1
(如果你在TSQL中进行条件事务,你应该跟踪(通过bool标志)你是否创建了事务 - 如果你这样做的话只有COMMIT
另一种选择是使用TransactionScope
- 更容易使用(您不需要针对每个命令设置它等),但稍微效率较低:
using(TransactionScope tran = new TransactionScope()) {
// create command, exec sp1, exec sp2 - without mentioning "tran" or
// anything else transaction related
tran.Complete();
}
(注意没有回滚等;如果需要,Dispose()
(通过using
)将进行回滚。
答案 1 :(得分:4)
如果您在应用程序中执行此操作,请不要在数据库/存储过程中执行事务!这肯定会造成混乱。选一层并坚持下去。确保您有一个很好的规范化数据库,异常应该向上渗透。
答案 2 :(得分:1)
我同意Marc的看法,问题很可能出在存储过程中。 有一篇非常有趣的文章概述了一些问题here。
答案 3 :(得分:0)
如果存储过程包含以下代码:
BEGIN TRY
SET @now = CAST(@start AS datetime2(0))
END TRY
BEGIN CATCH
SET @now = CURRENT_TIMESTAMP
END CATCH
你通过了例如'现在'作为@start,try中的CAST将失败。这标志着事务只是回滚,即使已经捕获并处理了错误本身。因此,虽然您没有从上面的代码中获得任何异常,但是无法提交事务。如果您的存储过程具有这样的代码,则需要重写它以避免try / catch。