Entity Framework Code First DbContext and SQL Azure Connection Fault Handling

Cesar De la Torre

image 

I’ve been working migrating an App to Windows Azure and SQL Azure. This app is using Entity Framework 4.3 Code First and DbContext.

One important point is related to the SQL Azure Conection Fault Handling. If you don’t know about this topic, you can read this explanatory info:

http://social.technet.microsoft.com/wiki/contents/articles/1541.sql-azure-connection-management-en-us.aspx

Then, regarding a workaround when using Entity Framework 4.0 (based on ObjectContext), I wrote this post a few months ago:

http://blogs.msdn.com/b/cesardelatorre/archive/2010/12/20/handling-sql-azure-connections-issues-using-entity-framework-4-0.aspx 

Also, this link, provided by the AppFabric Customer Advisory Team, is quite useful, too:

http://blogs.msdn.com/b/appfabriccat/archive/2010/12/11/sql-azure-and-entity-framework-connection-fault-handling.aspx

But, this info was all related to EF 4.0, ObjectContext, EF Templates (STE & POCO T4templates, etc.), not about EF 4.3, CODE FIRST APPROACH and DbContext.

Therefore, this post is about how to handle SQL Azure Connection Faults (something common in the Windows Azure platform) when using EF 4.3, DbContext and CODE FIRST APPROACH.

THE ISSUE:

If you take a look to the link provided by the AppFabric Customer Advisory Team, about the ‘Retry Policy’ when creating the EF Context. The tactic is to implement the OnContextCreated() partial method of your model context (based on ObjectContext) which is called each time a new context is instantiated. In this partial method, you employ a retry policy which opens a connection, submits a dummy query and handles exceptions with proper closure of invalid connections. In this way, the pool is ‘cleansed’ of all connections that have disconnected due to network glitches or idle expirations.

This was the initial suggested code for EF 4.0 and ObjectContext:

 

partial void OnContextCreated()
{
    int MaxRetries = 10;
    int DelayMS = 100;

    RetryPolicy policy = new RetryPolicy<SqlAzureTransientErrorDetectionStrategy>(MaxRetries, TimeSpan.FromMilliseconds(DelayMS));
    policy.ExecuteAction(() =>
    {
        try
        {
            string ss = Connection.ConnectionString;
            Connection.Open();
            var storeConnection = (SqlConnection)((EntityConnection)Connection).StoreConnection;
            new SqlCommand(“declare @i int”, storeConnection).ExecuteNonQuery();
            // throw new ApplicationException(“Test only”);
        }
        catch (Exception e)
        {
            Connection.Close();
            throw e;
        }
    }
    );
}

The issue is that this is based on the OnContextCreated() method which is related to your ObjectContext class.

When using CODE-FIRST approach, we use a context based on the DbContext, so we’ll have a slightly different code.

Instead of placing that code into the OnContextCreated() we can put it within the constructor of your model context (in this case based on DbContext).

Take into account that we’re using the RetryPolicy class, which is implemented in a special building block made by Microsoft Pattern & Practices team, called ‘Transient Fault Handling Application Block’ (good job Grigori & P&P team!).

It is available from NuGet: http://nuget.org/packages/EnterpriseLibrary.WindowsAzure.TransientFaultHandling

See also:

http://blogs.msdn.com/b/agile/archive/2011/12/02/announcing-the-enterprise-library-integration-pack-for-windows-azure-with-autoscaling-transient-fault-handling-and-more.aspx 

Anyway, this would be the first version of the code for EF & DbContext (Still, it doesn’t work):

using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;

using System.Data.Common;
using System.Data.SqlClient;
using System.Data.EntityClient;
using Microsoft.Practices.TransientFaultHandling;
using Microsoft.Practices.EnterpriseLibrary.WindowsAzure.TransientFaultHandling.SqlAzure;

public class MyBCUnitOfWork : DbContext
{
    //Constructor where we handle ‘SQL Azure and Entity Framework Connection Fault Handling’
    public MyBCUnitOfWork()
        : base()
    {
        DbConnection currentDbConn = this.Database.Connection;
       
        int MaxRetries = 10;
        int DelayMS = 100;

        RetryPolicy policy = new RetryPolicy<SqlAzureTransientErrorDetectionStrategy>(MaxRetries, TimeSpan.FromMilliseconds(DelayMS));
        policy.ExecuteAction(() =>

        {
            try
            {
                currentDbConn.Open(); //(Issue here!!)               

                var storeConnection = (SqlConnection)currentDbConn;
                new SqlCommand(“declare @i int”, storeConnection).ExecuteNonQuery();
                // throw new ApplicationException(“Test only”);
            }
            catch (Exception e)
            {
                currentDbConn.Close();
                throw e;
            }
        }
        );

    }

But, that is not the only difference we need to make, because if you only put that code within the constructor, when you execute your app, you’ll get an error when executing any EF code (your Linq sentences in any real DB access you do):

Exception mesage: “EntityConnection can only be constructed with a closed DbConnection

This issue is due to some internal changes in EF 4.3 DbContext, and it is explained by my friend Diego Vega in the following post:

Exception from DbContext API: EntityConnection can only be constructed with a closed DbConnection

http://blogs.msdn.com/b/diego/archive/2012/01/26/exception-from-dbcontext-api-entityconnection-can-only-be-constructed-with-a-closed-dbconnection.aspx

It is a very nice explanatory information about that issue. In any case, the solution for that exception is to open the connection using the ObjectContext object instead of the DbConnection.

Instead of this line:

currentDbConn.Open(); //(Issue here!!)               

We use this other line of code:

((IObjectContextAdapter)this).ObjectContext.Connection.Open();

Then, we’ll be able to run all the workaround within a DbContext constructor, like the final code below:

public class MyBCUnitOfWork
    : DbContext, IQueryableUnitOfWork

{
    //Constructor where we handle ‘SQL Azure and Entity Framework Connection Fault Handling’
    public MyBCUnitOfWork()
        : base()
    {
        //SQL Azure and Entity Framework Connection Fault Handling
        //See: http://blogs.msdn.com/b/appfabriccat/archive/2010/12/11/sql-azure-and-entity-framework-connection-fault-handling.aspx?Redirected=true
        DbConnection currentDbConn = this.Database.Connection;
       
        int MaxRetries = 10;
        int DelayMS = 100;

        RetryPolicy policy = new RetryPolicy<SqlAzureTransientErrorDetectionStrategy>(MaxRetries, TimeSpan.FromMilliseconds(DelayMS));
        policy.ExecuteAction(() =>
        {
            try
            {
                //(CDLTLL) Using ObjectContext.Connection because there’s an issue when using DbConnection.Open() here.
                //See this blog post from Diego Vega:
                //http://blogs.msdn.com/b/diego/archive/2012/01/26/exception-from-dbcontext-api-entityconnection-can-only-be-constructed-with-a-closed-dbconnection.aspx
                ((IObjectContextAdapter)this).ObjectContext.Connection.Open();

                var storeConnection = (SqlConnection)currentDbConn;
                new SqlCommand(“declare @i int”, storeConnection).ExecuteNonQuery();
                // throw new ApplicationException(“Test only”);
            }
            catch (Exception e)
            {
                currentDbConn.Close();
                throw e;
            }
        }
        );

    }

With that, we would have the ‘Retry Policy’ implemented when creating any DbContext object, so you employ a retry policy which opens a connection, submits a dummy query and handles exceptions with proper closure of invalid connections. In this way, the connection-pool is ‘cleansed’ of all connections that have disconnected due to network glitches, Sql Azure cluster-fail-over or idle expirations.

0 comments

Discussion is closed.

Feedback usabilla icon