How to set CommandTimeout for DbContext?
Asked Answered
E

10

79

I am looking a way to set CommandTimeout for DbContext. After searching I found the way by casting DbContext into ObjectContext and setting value for CommandTimeout property of objectContext.

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

But I have to work with DbContext.

Emmyemmye answered 11/5, 2012 at 10:26 Comment(1)
For EF Core see #39058922Opine
B
107

It will work with your method.

Or subclass it (from msdn forum)

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;
  }
}
Baler answered 11/5, 2012 at 10:30 Comment(4)
What's the point of using as? Won't that just turn an InvalidCastException into a NullReferenceException? I think the former is a lot clearer.Maxi
Is there any way of setting this from a partial class? I am working with EDMX files and want to avoid having this overwritten whenever I alter the model.Karoline
See Perry Tribolet's solution. Much simpler.Peterman
If like me you were wondering, CommandTimeout is expressed in seconds.Gamelan
E
45
var ctx = new DbContext();
ctx.Database.CommandTimeout = 120;
Ethereal answered 25/3, 2016 at 16:17 Comment(5)
This is the best solution in my opinion. By using this method, you can control the timeout for each command.Fissiparous
I'm on this page because I have production code doing this, and the setting is being ignored.Garlaand
Note that this value does not appear to be propagated to the CommandTimeout property of a DbCommand created via Connection.CreateCommand for the Connection property of that very same context. You may need to set it manually.Groggy
I'm not seeing a CommandTimeout property on DbContext.Database, using EF5 :( I do see other properties though. I do see a connection timeout setting though: DbContext.Database.Connection.ConnectionTimeoutAblation
@Groggy Your comment should be part of the documentation. Or it should be reported as a bug. Such an unintuitive behaviour... Do you know if there's a GitHub issue for this ?Batfish
N
21

This may help you.

public class MyContext : DbContext
{    
    public MyContext () : base(ContextHelper.CreateConnection("my connection string"), true)
    {
        ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = 300;
    }
}
Nissa answered 11/5, 2012 at 10:34 Comment(0)
D
10

I find that changing the .tt file works for me as I don't lose the change later on:

Add this line:

((IObjectContextAdapter)this).ObjectContext.CommandTimeout = 300;

Right after the DbContext creator and before the !loader.IsLazy construct:

<#=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))

It should then appear in your generated Context.cs:

public MyEntities()
            : base("name=MyEntities")
        {
            ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = 300;
        }
Darius answered 9/6, 2015 at 14:58 Comment(1)
This is the better, scalable, answer.Lolita
J
3

Here's how I solved this problem when using an EDMX file. This solution changes the default T4 template to make the generated class inherit from a custom DbContext class, which specifies a default command timeout, and a property to change it.

I'm using Visual Studio 2012 and EF 5.0. Your experience may differ with other versions.

Create a custom DbContext class

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;
        }
    }
}

This has an optional feature: I'm not hard-coding the default command timeout. Instead, I'm loading it from the project settings so that I can change the value in a config file. How to setup and use project settings is not in the scope of this answer.

I'm also not hard-coding the connection string or connection string name. It's already passed into the constructor by the generated context class, so it makes no sense to hard-code it here. This is nothing new; the EDMX file already generates the following constructor for you, so we are just passing along the value.

public MyEntities()
    : base("name=MyEntities")
{
}

(This instructs EF to load the connection string named "MyEntities" from the config file.)

I'm throwing a custom exception if the ObjectContext is ever null. I don't think it ever will be, but it's more meaningful than getting a NullReferenceException.

I store the ObjectContext in a field so that I can make a property to access it to override the default.

Modifying the entity context T4 template

In the Solution Explorer, expand the EDMX file so that you see the T4 templates. They have a .tt extension.

Double click the "MyModel.Context.tt" file to open it. Around line 57 you should see this:

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

This template line generates the class definition of your "MyEntities" class, which inherits DbContext.

Change the line so that the generated class inherits CustomDbContext, instead:

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

As soon as you save this file it should regenerate the class. If not, you can right-click the EDMX file and select "Run Custom Tool". If you expand the "MyModel.Context.tt" file under your EDMX file, you will see "MyModel.Context.cs". That's the generated file. Open it, and you should see that it now inherits CustomDbContext.

public partial class MyEntities : CustomDbContext

That's all there is to it.

Issues

Once you change the context class from DbContext to CustomDbContext, Visual Studio will give you an error if you try to add a new MVC controller class using the "Controller with read/write actions and views, using Entity Framework" template. It will say "Unsupported context type.". To get around this, open the generated "MyModel.Context.cs" class, and temporarily change the type it inherits back to DbContext. After adding your new controller, you can change it back to CustomDbContext.

Joan answered 5/6, 2015 at 15:26 Comment(0)
P
2

I like the extension approach:

public static class DbContextExtensions
{
   public static void SetCommandTimeout(this ObjectContext dbContext,
       int TimeOut)
   {
       dbContext.CommandTimeout = TimeOut;
   }
}

and then simply

((IObjectContextAdapter)cx).ObjectContext.SetCommandTimeout(300);
Pavkovic answered 29/12, 2014 at 17:20 Comment(0)
I
1

If it can help, this is the VB.Net solution:

Dim objectContext As Objects.ObjectContext = CType(Me,IObjectContextAdapter).ObjectContext
objectContext.commandTimeout = connectionTimeout
Illampu answered 26/3, 2013 at 10:27 Comment(0)
S
1

This is similar to the approach used by @Glazed above but my approach is also to use a custom DbContext class, but I am doing the reverse. Instead of modifying the T4 template (.tt file under your .edmx), I actually inherit from the resulting MyEntities Class instead like so:

MyEntities class generated by the T4 Template:

public partial class MyEntities : DbContext
{
    public MyEntities()
        : base("name=MyConnectionStringName")
    {
    }
...
}

Then create a new custom class as a wrapper around MyEntities like the following:

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;
    }
}

In your code, instantiate the Container class and if you need to use a custom timeout for a specific command, set it manually using the provided methods.

using (var db = new MyEntitiesContainer()) {
    db.SetCommandTimeout(300);
    db.DoSomeLongCommand();
    db.ResetCommandTimeout();
    db.DoShorterCommand1();
    db.DoShorterCommand2();
    ...
}

The benefit to this approach is that you can also create an interface for your Container class and use instances of the interface with dependency injection, then you can mock up your database in your unit tests in addition to having easier control over the command timeout and other properties of the object context that you can create methods for (such as lazy loading, etc).

Spradling answered 25/2, 2021 at 20:45 Comment(0)
P
0

I came here looking for an example of setting the timeout for a single command rather than such a global setting.

I figure that it will probably help someone to have an example of how I achieved this:

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();
Popular answered 12/8, 2016 at 9:20 Comment(0)
A
0

@PerryTribolet's answer looks good for EF6 but it does work for EF5. For EF, here is one way to do it: create an ObjectContext, set the CommandTimeout on that and then create a DBContext from the ObjectContext. I set the flag to have both objects disposed of together. Here is an example in 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

You don't have to use "With" of course.

Ablation answered 2/10, 2020 at 11:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.