SQLCLR 存储过程中的“MS DTC 已取消分布式事务”错误

Posted

技术标签:

【中文标题】SQLCLR 存储过程中的“MS DTC 已取消分布式事务”错误【英文标题】:"MS DTC has cancelled the distributed transaction" error in SQLCLR stored procedure 【发布时间】:2016-04-01 09:16:54 【问题描述】:

我需要将一些代码从旧项目移到新项目。旧项目使用带有存储过程(32 位版本)的 DLL,但我在 64 位 SQL Server 上需要这个 DLL,所以我需要重写这些过程。

我正在使用 SQL Server 2008 的存储过程编写 dll。在 Management Studio 中,我加载程序集,然后使用以下方法创建过程:

CREATE PROCEDURE ...
AS EXTERNAL NAME 

旧的 DLL 过程只是使用与远程 SQL Server 的新连接在其上运行存储过程并返回结果。

所以在我的过程中,我为远程服务器创建了一个SqlConnection 并在远程服务器上运行存储过程:

using (SqlConnection connection = new SqlConnection(String.Format("User ID=0;Password=1;Persist Security Info=True;Initial Catalog=2;Data Source=3", Login, Password, DBName, ServerName)))

    connection.Open();
    SqlCommand command = new SqlCommand("Exec ProcName", connection);
    SqlDataReader reader = command.ExecuteReader();
    SqlContext.Pipe.Send(reader);

如果我在 SSMS 中运行此过程,它会起作用。但是在旧项目中它会引发错误:

Microsoft 分布式事务协调器 (MS DTC) 已取消分布式事务。

MSDTC 服务运行,我设置了所有安全参数。如何解决?也许还有其他方法可以运行远程存储过程(链接服务器),但我需要保存旧的项目功能。

【问题讨论】:

DTC 涉及事务发布开始事务的代码。您的存储过程做了一些非常邪恶的事情——它打开了一个新连接 服务器,这意味着由您的代码启动的任何现有事务都必须使用 DTC 升级为分布式事务。为什么不直接创建链接服务器并直接调用远程过程?为了运行该代码,您还必须放松 SQLCLR 安全性 【参考方案1】:

这里有几件事不太对劲:

    你为什么要重写任何东西?如果你有代码,最坏的情况就是为新架构重新编译。

    你为什么要做任何事情?代码应针对“任何 CPU”(在“项目属性”的“SQLCLR 构建”选项卡中的“平台目标”下)编译,而不是专门针对 32 位或 64 位。如果它已经在“Any CPU”下编译,那么就没有什么可做的了。在开始重新编译和/或重写之前,您是否在新系统上进行了测试?

    不要使用String.Format 创建连接字符串。相反,请使用SqlConnectionStringBuilder:

    SqlConnectionStringBuilder _ConnectionStringBuilder =
                                     new SqlConnectionStringBuilder();
    
    _ConnectionStringBuilder.DataSource = ServerName;
    _ConnectionStringBuilder.InitialCatalog = DBName;
    _ConnectionStringBuilder.UserID = Login;
    _ConnectionStringBuilder.Password = Password;
    

    除非您绝对别无选择并且必须使用此选项,否则不要指定Persist Security Info=True;

    不要使用new SqlCommand(),而是使用以下方法创建SqlCommand

    using(SqlCommand command = connection.CreateCommand())
    
      command.CommandText = "Exec ProcName";
    
    

    确保还指定command.CommandType = CommandType.StoredProcedure;,以便它执行实际的 RPC 调用而不是临时查询。这将要求您从“EXEC ProcName”的当前CommandText 中删除“EXEC”的文本;你只能指定[[DatabaseName.]SchemaName.]ProcName

    SqlDataReader 是一次性对象,就像SqlConnectionSqlCommand,所以SqlDataReader reader = command.ExecuteReader() 应该被包裹在using() 构造中。

一旦上述项目得到纠正,您应该能够通过简单地设置 SqlConnectionStringBuilder 的以下属性来修复错误:_ConnectionStringBuilder.Enlist = false

有关使用 SQLCLR 的更多详细信息和示例,请参阅我在 SQL Server Central 上就该主题撰写的系列文章:Stairway to SQLCLR(需要免费注册才能阅读该站点上的内容)。

【讨论】:

1.我只有旧 DLL 的代码,而不是整个项目。这段代码在 Pascal 上。在 XE6(到 64 位)下编译导致类型问题,我的团队决定编写新库而不是重构旧代码。 3-7 同意,谢谢!如果我使用 StoredProcedure 类型,我只需从命令文本中删除 Exec @cerberus 是的,很抱歉没有提到删除EXEC,因为它只能是[[DatabaseName.]SchemaName.]ProcName。我忘了那是在那里,因为我试图不忘记其他细节;-)。我将更新我的答案以包含该位。但是,旧代码是如何在 Pascal 中完成的?你是说德尔福XE6吗? XE6 是否编译为 .NET MSIL?根据embarcadero.com/products/delphi/faq 它没有。还是我错过了什么? 旧代码是Delphi 7项目,它使用ODS API。所以它与.net没有任何关系。我们尝试将 XE6 构建到 64 位平台,但是类型有很多错误。所以我们决定用c#写 @cerberus 那么也许我完全误解了旧项目。旧项目是作为 SQLCLR 完成的吗?我想此时不会,因为那需要.NET。我原本以为你是在更新 SQLCLR 项目而不是移植到 SQLCLR。 我有使用 ODS API 的 delphi-application 和 delphi dll(32 位)。现在我需要将此 dll 函数移植到 c# dll 并且因为 ODS 很旧,我使用 sqlclr

以上是关于SQLCLR 存储过程中的“MS DTC 已取消分布式事务”错误的主要内容,如果未能解决你的问题,请参考以下文章

SQL触发器调用.NET的类方法续SQLCLR应用

Visual Studio 2010 中 SQLCLR 项目中的计算列

基于SQL Server版本的SQL CLR条件编译

无法连接到 SQLCLR 中的外部数据库

SQLCLR:如何将程序集 Npgsql.dll 添加到数据库?

SQLCLR 的使用及开启基础