C# 触发器中的SQL Server错误,该错误使用SqlDataAdapter和环境事务锁定丢失的表

C# 触发器中的SQL Server错误,该错误使用SqlDataAdapter和环境事务锁定丢失的表,c#,sql-server,error-handling,transactions,transactionscope,C#,Sql Server,Error Handling,Transactions,Transactionscope,好吧,我遇到了一个很奇怪的情况。我的处境有好几层。我还没有确定是否每个层都是严格要求的,但下面是发生的情况: C#code正在创建一个环境事务,一个SqlConnection将自动登记到该事务中 C#code使用SqlDataAdapter将行插入表中 InsertCommand正在引用存储过程。存储过程是一个简单的INSERT语句 正在执行插入操作的表上有一个触发器,而不是INSERT 触发器获得表上的独占锁 触发器内发生错误 通过这种连接,错误不会在C代码中出现。但是,如果触发器没有获得

好吧,我遇到了一个很奇怪的情况。我的处境有好几层。我还没有确定是否每个层都是严格要求的,但下面是发生的情况:

  • C#code正在创建一个环境事务,一个
    SqlConnection
    将自动登记到该事务中
  • C#code使用
    SqlDataAdapter
    将行插入表中
  • InsertCommand
    正在引用存储过程。存储过程是一个简单的
    INSERT
    语句
  • 正在执行
    插入操作的表上有一个触发器
    ,而不是INSERT
  • 触发器获得表上的独占锁
  • 触发器内发生错误
通过这种连接,错误不会在C代码中出现。但是,如果触发器没有获得表上的排他锁,则错误会补足C代码

不过,在SQL Server端,事务已被中止,这一事实证明了错误实际上正在发生。C#代码不知道事务已中止,只有在处置
TransactionScope
尝试
提交事务时才会遇到错误

我创建了此场景的最小复制:


有人知道为什么会出现这种情况,以及如何恢复正确的错误处理行为吗?

因此,我已经对此做了更多的测试

我的第一个想法是,如果持有独占锁导致它抑制错误,那么明确地释放锁可能会抑制错误?因此,我在概念证明中生成错误的语句周围放置了一个
TRY
/
CATCH
,让它
回滚事务
,然后重新抛出
,但它什么也没做

因此,我的下一个想法是,
RAISERROR
语句与严重性级别20-25一起使用时,会强制终止连接。我不确定这是否是一个理想的解决方案,因为发生这种情况时,它还会将一个条目写入SQL Server事件日志。但是,它确实实现了让
SqlDataAdapter
在其
Update
命令期间查看错误的目标,而不是让C代码认为事务仍然处于活动状态并尝试提交它


是否有人知道这种“大锤式”方法的其他潜在缺点,或者它可能是在这种情况下使错误正确传播的唯一方法?

我已经确定了问题的原因

触发器中锁定表的语句如下所示:

SELECT TOP 0 *
  FROM TableToTriggerAndLock WITH (TABLOCKX, HOLDLOCK)
虽然它不返回任何数据,但它确实返回一个(空)结果集。事实证明,
SqlDataAdapter
类只关心它在TDS流中返回的第一个结果集,因此返回到第二个结果集中的错误被完全忽略

取出locking语句,然后取出冗余的结果集,现在错误出现在第一个结果集中

然后,解决方案是抑制结果集,我通过将锁定语句修改为:

DECLARE @Dummy INT

SELECT TOP 0 @Dummy = 1
  FROM TableToTriggerAndLock WITH (TABLOCKX, HOLDLOCK)

希望这能帮助其他人使用
SqlDataAdapter
和更复杂的底层操作。:-)

任何看过这篇文章并被
数据库初始化.sql
和代码之间的不匹配搞糊涂的人,这是我的错。在添加和提交到git之前,我忘了点击
Save
。我已经使用正确的初始化代码进行了后续提交。