如何为DbContext设置CommandTimeout?

77

我想找到一种设置DbContext CommandTimeout的方法。搜索后,我发现可以将DbContext转换为ObjectContext,并为objectContext的CommandTimeout属性设置值。

var objectContext = (this.DbContext as IObjectContextAdapter).ObjectContext;

但是我必须使用DbContext。


1
对于 EF Core,请参见 https://dev59.com/UlkT5IYBdhLWcg3wG74_ - Michael Freidgeim
10个回答

106

你的方法可以起作用。

或者从msdn论坛中继承它。

public class YourContext : DbContext
{
  public YourContext()
    : base("YourConnectionString")
  {
    // Get the ObjectContext related to this DbContext
    var objectContext = (this as IObjectContextAdapter).ObjectContext;

    // Sets the command timeout for all the commands
    objectContext.CommandTimeout = 120;
  }
}

19
使用as的目的是什么?这样不会把“InvalidCastException”变成“NullReferenceException”吗?我认为前者更清晰。 - Sam
有没有办法从部分类中设置这个?我正在使用EDMX文件,并希望在修改模型时避免被覆盖。 - Bpainter
2
看看 Perry Tribolet 的解决方案。简单多了。 - Fabrice
2
如果你和我一样好奇,CommandTimeout是以秒为单位表示的。 - Alex Sanséau

44
var ctx = new DbContext();
ctx.Database.CommandTimeout = 120;

这是我认为最好的解决方案。通过使用这种方法,您可以控制每个命令的超时时间。 - clockwiseq
6
我在这个页面上是因为我有生产代码在执行这个操作,但设置被忽略了。 - PstScrpt
2
请注意,此值似乎未传播到通过Connection.CreateCommand创建的DbCommand的CommandTimeout属性,用于该上下文的Connection属性。您可能需要手动设置它。 - Triynko
我在使用EF5时,在DbContext.Database上没有看到CommandTimeout属性:( 但是我确实看到了其他属性。不过,我确实看到了一个连接超时设置:DbContext.Database.Connection.ConnectionTimeout。 - Zeek2
@Triynko你的评论应该成为文档的一部分。或者它应该被报告为一个bug。这样一个不直观的行为... 你知道这个问题在GitHub上是否有记录吗? - Julien Debache

21

这可能对你有所帮助。

public class MyContext : DbContext
{    
    public MyContext () : base(ContextHelper.CreateConnection("my connection string"), true)
    {
        ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = 300;
    }
}

10
我发现更改.tt文件对我很有效,因为以后我不会失去这个更改:
添加这一行:
((IObjectContextAdapter)this).ObjectContext.CommandTimeout = 300;

在 DbContext 创建器之后,!loader.IsLazy 构造函数之前:

<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
    public <#=code.Escape(container)#>()
        : base("name=<#=container.Name#>")
    {
        ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = 300;
<#
if (!loader.IsLazyLoadingEnabled(container))

它应该出现在您生成的Context.cs文件中。
public MyEntities()
            : base("name=MyEntities")
        {
            ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = 300;
        }

这是更好、可扩展的答案。 - AussieJoe

3
以下是我在使用EDMX文件时解决问题的方法。此解决方案更改默认的T4模板,使生成的类继承自自定义的DbContext类,该类指定了默认的命令超时时间以及一个可用于更改它的属性。
我使用的是Visual Studio 2012和EF 5.0。您在其他版本中的经验可能会有所不同。
创建自定义DbContext类:
public class CustomDbContext : DbContext
{
    ObjectContext _objectContext;

    public CustomDbContext( string nameOrConnectionString )
        : base( nameOrConnectionString )
    {
        var adapter = (( IObjectContextAdapter) this);

        _objectContext = adapter.ObjectContext;

        if ( _objectContext == null )
        {
            throw new Exception( "ObjectContext is null." );    
        }

        _objectContext.CommandTimeout = Settings.Default.DefaultCommandTimeoutSeconds;
    }

    public int? CommandTimeout
    {
        get
        {
            return _objectContext.CommandTimeout;
        }
        set
        {
            _objectContext.CommandTimeout = value;
        }
    }
}

这个功能是可选的:我没有硬编码默认命令超时时间,而是从项目设置中加载它,以便我可以在配置文件中更改该值。如何设置和使用项目设置不在本答案的范围内。
我也没有硬编码连接字符串或连接字符串名称。已经通过生成的上下文类传递给构造函数,因此在这里硬编码它是没有意义的。这并不是什么新鲜事物;EDMX文件已为您生成了以下构造函数,因此我们只是将该值传递下去。
public MyEntities()
    : base("name=MyEntities")
{
}

(这指示EF从配置文件中加载名为“ MyEntities”的连接字符串。)
如果ObjectContext为空,我会抛出自定义异常。虽然我不认为它会为空,但这比获得NullReferenceException更有意义。
我将ObjectContext存储在字段中,以便可以创建属性来访问它以覆盖默认设置。

修改实体上下文T4模板

在解决方案资源管理器中展开EDMX文件,以查看T4模板。它们具有.tt扩展名。
双击“ MyModel.Context.tt”文件以打开它。大约在第57行附近,您应该看到以下内容:
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext

这个模板行生成了你的“MyEntities”类的类定义,它继承自DbContext。

将该行更改为生成的类继承CustomDbContext:

<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : CustomDbContext

一旦您保存此文件,它应该重新生成类。如果没有重新生成,您可以右键单击EDMX文件,然后选择“运行自定义工具”。如果您在EDMX文件下展开“MyModel.Context.tt”文件,您将看到“MyModel.Context.cs”。那是生成的文件。打开它,您应该会看到它现在继承了CustomDbContext

public partial class MyEntities : CustomDbContext

就是这样。

问题

一旦您将上下文类从 DbContext 更改为 CustomDbContext,如果您尝试使用“使用 Entity Framework 的读/写操作和视图控制器”模板添加新的 MVC 控制器类,Visual Studio 将会给出一个错误。它将说“不支持的上下文类型”。为了解决这个问题,打开生成的“MyModel.Context.cs”类,并暂时将其继承类型更改回 DbContext。添加完新的控制器后,可以将其改回 CustomDbContext


2
我喜欢扩展方法的思路:
public static class DbContextExtensions
{
   public static void SetCommandTimeout(this ObjectContext dbContext,
       int TimeOut)
   {
       dbContext.CommandTimeout = TimeOut;
   }
}

然后只需简单地执行此操作。
((IObjectContextAdapter)cx).ObjectContext.SetCommandTimeout(300);

1
这类似于@Glazed上面使用的方法,但我的方法也是使用自定义DbContext类,但我是相反的。我不是修改T4模板(.edmx下的.tt文件),而是从生成的MyEntities类继承,如下所示:
由T4模板生成的MyEntities类:
public partial class MyEntities : DbContext
{
    public MyEntities()
        : base("name=MyConnectionStringName")
    {
    }
...
}

然后创建一个新的自定义类,作为MyEntities的包装器,如下所示:

public class MyEntitiesContainer : MyEntities
{
    private static readonly int _DEFAULT_TIMEOUT = 100;
    public MyEntitiesContainer()
    {
        ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = _DEFAULT_TIMEOUT;
    }

    //Use this method to temporarily override the default timeout
    public void SetCommandTimeout(int commandTimeout)
    {
        ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = commandTimeout;
    }

    //Use this method to reset the timeout back to default
    public void ResetCommandTimeout()
    {
        ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = _COMMAND_TIMEOUT;
    }
}

在您的代码中,实例化Container类,如果需要为特定命令使用自定义超时时间,则使用提供的方法手动设置它。
using (var db = new MyEntitiesContainer()) {
    db.SetCommandTimeout(300);
    db.DoSomeLongCommand();
    db.ResetCommandTimeout();
    db.DoShorterCommand1();
    db.DoShorterCommand2();
    ...
}

这种方法的好处是您还可以为Container类创建一个接口,并使用依赖注入的实例,然后您可以在单元测试中模拟数据库,除了更容易控制命令超时和对象上下文的其他属性外,您还可以为其创建方法(如惰性加载等)。

1
如果有帮助的话,这是VB.Net的解决方案:
Dim objectContext As Objects.ObjectContext = CType(Me,IObjectContextAdapter).ObjectContext
objectContext.commandTimeout = connectionTimeout

0

我来这里是为了寻找一个设置单个命令超时时间的例子,而不是全局设置。

我想这个例子可能会对某些人有所帮助,下面是我实现它的示例:

var sqlCmd = new SqlCommand(sql, context.Database.Connection as SqlConnection);
sqlCmd.Parameters.Add(idParam);
sqlCmd.CommandTimeout = 90;

if (sqlCmd.Connection.State == System.Data.ConnectionState.Closed)
{
    sqlCmd.Connection.Open();
}
sqlCmd.ExecuteNonQuery();
sqlCmd.Connection.Close();

0

@PerryTribolet的答案对于EF6看起来不错,但它不适用于EF5。对于EF,有一种方法可以做到这一点:创建一个ObjectContext,设置该对象的CommandTimeout,然后从ObjectContext创建一个DBContext。我设置了标志,使两个对象一起被处理。以下是VB.NET的示例:

        Dim context As New ObjectContext("name=Our_Entities")
        Dim dbcontext As New System.Data.Entity.DbContext(context, True)

        With context
            .CommandTimeout = 300 'DBCommandTimeout
        End With

当然你不必使用 "With"。


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