19 November 2013

Entity Framework 5 to 6.0.1 Upgrade SqlProviderServices Exception

Today, I decided to update my Entity Framework (EF) 5.0 NuGet packages to EF 6.0.1. No problem updating; NuGet did its duty, as usual. However, a great number of the Model project tests failed, and executing the application threw an exception, as expected:

The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer' registered in the application config file for the ADO.NET provider 
with invariant name 'System.Data.SqlClient' could not be loaded. Make sure that the assembly-qualified name is used and that the assembly is available to the running application. 
See http://go.microsoft.com/fwlink/?LinkId=260882 for more information.

As usual, the link provided minimal assistance. With a little prodding, I found a workaround for the issue.

The goal of this post is to make as few modifications to the code base as possible, to make the EF5 to EF6 upgrade. Sure, there are some differences between EF5 and EF6 implementation, but we're ignoring that for now. EF6 does not require data provider configurations in the App.config file. The new ProviderService types, embedded in the System.Data.Entity.(ProviderName) namespace does this for us. But, these are the objects giving us trouble. First things first...

Fixing the SqlProviderServices Issue

STEP 1: Comment out the existing Entity Framework element, and replace it with the new, EF6 version. You'll notice my project is also using the SQL Server Compact Edition (SQL CE or SqlCE) provider, and the default connection factory is configured. These may also be removed; the application programmatically updates the DefaultConnectionFactory and AppDomain default connection string, after dynamically generating the secure connection string (more on this, later):


<entityFramework>
 <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlCeConnectionFactory, EntityFramework">
  <parameters>
   <parameter value="System.Data.SqlServerCe.4.0" />
  </parameters>
 </defaultConnectionFactory>
 <providers>
  <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
  <provider invariantName="System.Data.SqlServerCe.4.0" type="System.Data.Entity.SqlServerCompact.SqlCeProviderServices, EntityFramework.SqlServerCompact" />
 </providers>
</entityFramework>
 

<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />

STEP 2: If you're reading this post, you've probably already done step 1 and things still aren't working. There's another step! Past experience with these types of issues taught me that sometimes things need a little priming, before they work properly. If you don't already have one, create a static constructor in your DBContext derivative class. The EF6 providers must be called, before instantiating a DBContext derivative class. The first constructor to be called is generally the static constructor.

static MyDbContext()
{
  // EF 6.0.1 throws an exception, unless we first probe the provider types.
  var type1 = typeof(System.Data.Entity.SqlServer.SqlProviderServices);
  var type2 = typeof(System.Data.Entity.SqlServerCompact.SqlCeProviderServices);
  ...
}

This immediately resolved the issue. However, I heard my inner software craftsman shouting me that ALL of the constructors should call these lines of code, and should adhere to the DRY Principle. So, how do we call a common initialization method from both the static and instance constructors?

STEP 3 THE REAL STEP 2: We simply need to call a different class initialization method, from the static constructor. The initializer instantiates the class, and then allows calls to instance methods. So, create an Initialze method. You'll see that mine also contains code to configure other things. Please note the type probes have been moved to the Initialize method.

Of course, if you already have redirected your initializer to a custom method, simply add the type probes to your initialize method.

public class LocalDbContext : DbContextILocalDbContext
{
  private bool _isInitialized = false;
 
  // Be sure to call Initialize from the instance constructors!
  private LocalDbContext() : base()
  {
    Initialize();
  }
 
  static MyDbContext()
  {
    // The following replaces the original "Database.SetInitializer<MyDbContext>(strategy);".
    var strategy = new MyDbContextInitializer();
    var init = strategy as IDatabaseInitializer<MyDbContext>;
    Database.SetInitializer<MyDbContext>(strategy);
  }
 
  protected internal void Initialize()
  {
    if (!m_isInitialized)
    {
      _isInitialized = true;
 
      // EF 6.0.1 throws an exception, unless we first probe the provider types.
      var type1 = typeof(System.Data.Entity.SqlServer.SqlProviderServices);
      var type2 = typeof(System.Data.Entity.SqlServerCompact.SqlCeProviderServices);
    }
  }

Now, we have a workaround that functions from every constructor of the DbContext derivative, with minimal impact to existing code.

5 comments:

  1. Actually the issue appears to be with the TestRunner. I found that any tests that used the [DeploymentItem] attribute would fail. This can be addressed by referencing System.Data.Entity.SqlServer.SqlProviderServices.Instance in the test assembly or by specifying the EntityFramework.SqlServer assembly as a deployment item. For more info see: http://entityframework.codeplex.com/workitem/1590

    ReplyDelete
  2. Step 1 Fixed my problem!

    Thanks Mike.
    Neeraj

    ReplyDelete
  3. The type or namespace name 'SqlServerCompact' does not exist in the namespace 'System.Data.Entity' (are you missing an assembly reference?)
    All assemblies are referenced

    ReplyDelete
  4. Thanks! Step 1 fixed the problem for me as well!

    ReplyDelete
  5. Thanks million
    your code (Step2) really solved my EF6 init error

    ReplyDelete

Please provide details, when posting technical comments. If you find an error in sample code or have found bad information/misinformation in a post, please e-mail me details, so I can make corrections as quickly as possible.