TransactionScope最大超时时间

17

我在这段代码中使用了TransactionScope:

private void ExecuteSP()
{
    bool IsComplete = false;
    SqlCommand sqlComm = null;
    //6 hours!!!
    TimeSpan ts1 = new TimeSpan(6, 0, 0);
    try
    {
        using (TransactionScope t = new TransactionScope(TransactionScopeOption.RequiresNew, ts1))
        {
            using (SqlConnection sqlConn = new SqlConnection(GetConnectionString()))
            {
                //open sql connection
                sqlConn.Open();
                try
                {
                    //create new sqlCommand
                    sqlComm = new SqlCommand();
                    for (int i = 1; i <= 2; i++)
                    {
                        IsComplete = true;
                        //This command takes 15 minutes
                        sqlComm.CommandText = "exec TestSp";
                        sqlComm.Connection = sqlConn;
                        sqlComm.CommandType = CommandType.Text;
                        sqlComm.CommandTimeout = 18000;
                        //Executing my command
                        int j = sqlComm.ExecuteNonQuery();                       
                    }
                    //End
                    t.Complete();
                }
                catch (Exception ex)
                {
                    IsComplete = false;
                    string Message = ex.Message;
                }
                finally
                {
                    if (sqlComm != null)
                        sqlComm.Dispose();                 
                }
            }
        }
    }
    catch (Exception ex)
    {
        string messagee = ex.Message;
        //do something
    }
    finally
    {
        MessageBox.Show("Finsh");
    }
}

当执行sqlCommand.ExecuteNonQuery()超过10分钟时,会出现这种情况。在此点上,我没有收到任何异常,但在下一次执行时,我会收到以下异常:

The transaction associated with the current connection has completed but has not been disposed. The transaction must be disposed before the connection can be used to execute SQL statements.

这是因为System.Transactions.TransactionManager.MaximumTimeout被设置为10分钟的TimeSpan。

我搜索并发现可能与“System.Transactions-->machine.config的maxTimeout”有关,但在更改配置文件后,我收到了一个异常:

<?xml version="1.0"?>
<configuration> 
  <system.transactions>
    <machineSettings maxTimeout="10:00:00"/>
  </system.transactions> 
  <appSettings>    
    <add key="FileName" value="MyFileName" />
    <add key="MySpace" value="5 MB" />       
    <add key="ClientSettingsProvider.ServiceUri" value="" />        
  </appSettings>      
</configuration>

当我在运行时更改配置文件后尝试获取System.Transactions.TransactionManager.MaximumTimeout时,我遇到了以下异常:

"配置系统初始化失败"

有人知道如何解决这个问题吗?(关于我的情况的一般说明:我需要执行存储过程,该存储过程需要约20分钟,因为我需要将包含int的表转换为SQL中的bigint(int = 32位,bigint =64位)。我需要创建新表并将旧表中的数据插入到具有int64的新表中。该表通过ID连接到其他4个表,每个表都包含超过2000万行,并且还有绑定、索引等。我无法将此过程拆分为小型存储过程,因此我需要将最大超时时间更改为一小时或更长时间,10分钟不够!)

3
需要20分钟才能完成的存储过程?那就是问题所在。 - Grant Thomas
2
原子操作需要是原子的,无论它需要多长时间。当然,在许多情况下,您可以设计您的应用程序在内存或其他缓存中执行操作,然后尽快对数据库执行所有事务,但这通常是不切实际的。如果您正在使用数据库作为信息源,并且您的事务涉及对该数据模型进行更改(例如通过DataContext子类),那么未来事务中的计算将依赖于该更改,那么您可能会得到一个需要> 10分钟的事务。 - Triynko
6个回答

32

如果你不介意使用反射,你实际上可以编程地覆盖最大超时时间。该代码不能保证是面向未来的,但在 .NET 4.0 上可以正常工作。

public static class TransactionmanagerHelper
{
    public static void OverrideMaximumTimeout(TimeSpan timeout)
    {
        //TransactionScope inherits a *maximum* timeout from Machine.config.  There's no way to override it from
        //code unless you use reflection.  Hence this code!
        //TransactionManager._cachedMaxTimeout
        var type = typeof(TransactionManager);
        var cachedMaxTimeout = type.GetField("_cachedMaxTimeout", BindingFlags.NonPublic | BindingFlags.Static);
        cachedMaxTimeout.SetValue(null, true);

        //TransactionManager._maximumTimeout
        var maximumTimeout = type.GetField("_maximumTimeout", BindingFlags.NonPublic | BindingFlags.Static);
        maximumTimeout.SetValue(null, timeout);
    }
}
你可以像这样使用它:
            TransactionmanagerHelper.OverrideMaximumTimeout(TimeSpan.FromMinutes(30));

2
只是尝试一下,看起来示例代码中有一个错误。应该将“timeout”传递给SetValue以获取maximumTimeout,而不是使用Timespan.FromMinutes(30)。 - Adrian Russell
这是一个不错的技巧!谢谢。 - NeutronCode
1
@AdrianRussell 更新了答案。 - buckley

12

你无法在自己的配置文件中指定machineSettings,但你需要修改/添加计算机上machine.config文件中的machineSettings\maxTimeout

对于计算机上每个32/64位和CLR版本组合,都有一个该文件的实例。例如,.NET 2.0的32位版本文件位于%windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG目录中。64位.NET 2.0应用程序的文件位于%windir%\Microsoft.NET\Framework64\v2.0.50727\CONFIG目录中。同样地,如果您使用的是.NET 4.0,则需要更改v4.0.30319\Config子目录中的文件。

请注意,通过更改此文件(正如名称所示),将更改计算机上每个事务的最大超时时间。因此,在此处设置时间时要小心。在您的示例中,您已将超时时间更改为10(!)小时。

过长的事务超时时间可能会成为一个问题,因为有时在达到超时时间之前无法检测到死锁。因此,有时这些情况无法及时检测到。还有其他原因也应该避免运行时间过长的事务,但这超出了此问题/答案的范畴。

无论如何,单个应用程序仍然可以通过在其自己的配置文件中使用system.transactions部分来设置自己的最大超时时间,但这始终会受到machine.config文件中的超时时间限制。

  <system.transactions>
    <defaultSettings timeout="22:00:00"/>
  </system.transactions>
请注意,这里的元素名称是defaultSettings而不是machineSettings
你还可以查看以下链接以获取更多信息:

谢谢,但是由于defaultSetting受maxTimeout的限制,我尝试将maxTimeout添加到我的machineSetting(machine.config)中,但仍然出现异常:“配置系统初始化失败”(在异常内部: “{”在machine.config之外使用注册为allowExeDefinition ='MachineOnly'的部分是错误的。(C:\ ... Aplication {ath \ ApplicationName.exe.Config第5行)“}”。我该如何解决这个问题?如果我不想更改其他应用程序设置,只想在我的应用程序中进行更改...有没有办法做到这一点? - user436862
2
看起来你正在尝试将machineSetting元素放入本地配置文件中,甚至在异常消息中也是这样写的('C:\...Aplication{ath\ApplicationName.exe.Config line 5')。只需将其放入'machine.config'文件中即可(请参见我的答案第一段),然后你就可以了。 - Christian.K

0

我从你的代码中注意到一个与MS提供的示例不同的地方,那就是你在完成SQL工作之前就完成了事务。

例如:

 t.Complete(); 

在之前

            if (sqlComm != null)
                sqlComm.Dispose();  

你的 t.complete(); 真的需要移到 sql 段落的末尾。我还没有运行它来证明它是否有效,但是这很有道理,因为你有一个事务部分,然后在告诉它完成之后还有进一步的工作。

我使用了这个作为参考:http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx


谢谢,但我仍然得到相同的异常...还有其他想法吗? - user436862

0
尝试交换配置文件中元素的顺序,使得appSettingssystem.transactions之前,像这样:
<appSettings>    
  ...     
</appSettings> 
<system.transactions>
  ...
</system.transactions> 

此外,如果您的代码中有 configSections,也要同样处理。

我尝试过了,但初始化时仍然出现相同的异常:“配置系统无法初始化”... 我真的尝试了所有方法都没有成功... 有什么想法吗? - user436862
2
我曾遇到相同的配置异常,但当我将system.transactions标记移到我的machine.config文件底部时,一切都按预期工作了! - Thomas C. G. de Vilhena
谢谢@Thomas C. G. de Vilhena,将system.transactions标签移到底部对我也起作用了! - 03Usr

0

默认值为:

Transaction Binding=Implicit Unbind. 

隐式解绑会在连接结束时将其与事务分离。

在一些情况下,人们使用:

Binding=Explicit Unbind

你可以在你的情况下检查这个。


-3
尝试更改 machine.config 属性 allowExeDefinition 为 "MachineToApplication":
<sectionGroup name="system.transactions" type="System.Transactions.Configuration.TransactionsSectionGroup, System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, Custom=null">
            <section name="defaultSettings" type="System.Transactions.Configuration.DefaultSettingsSection, System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, Custom=null"/>
            <section name="machineSettings" type="System.Transactions.Configuration.MachineSettingsSection, System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, Custom=null" allowDefinition="MachineOnly" allowExeDefinition="MachineToApplication" />
</sectionGroup>

然后,在修改您的 web.config/app.config 文件:

<?xml version="1.0" encoding="utf-8"?>
   <configuration>
    ...
    <system.transactions>
        <machineSettings maxTimeout="00:00:00" ></machineSettings>
      </system.transactions>
    </configuration>

在属性“maxTimeout”中,值为00:00:00(或零)被解释为无限大。

此致敬礼,


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接