Вопрос

I have a class library with EF Code First. I just upgraded to EF 4.3 and now I want to enable migrations.

I type Enable-Migrations -ProjectName MyProjectName in the PM console but receive the following error

PM> Enable-Migrations -ProjectName MyProjectName
System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at System.Data.Entity.Migrations.DbMigrationsConfiguration.GetSqlGenerator(String providerInvariantName)
   at System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration, DbContext usersContext)
   at System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration)
   at System.Data.Entity.Migrations.Design.MigrationScaffolder..ctor(DbMigrationsConfiguration migrationsConfiguration)
   at System.Data.Entity.Migrations.Design.ToolingFacade.ScaffoldRunner.RunCore()
   at System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run()
The given key was not present in the dictionary.
PM> 

I cant figure out what dictionary that might be wrong.

My connection string looks like this:

<connectionStrings>
  <add name="MySystem" connectionString="Data Source=MyServer\Instance;Initial Catalog=myDbName;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>

Any idea about what might be wrong?

Just a note:
I use my class library in a console application with an exact copy of my app.config and there I can access my database perfectly well.

Это было полезно?

Решение

It turned out Anders Abel was right in the cause, but we found a much simpler solution.

According to the mvc-mini-profiler page there is a special package in Nuget called MiniProfiler.EF that does not requires any wrapper around the SqlConnection. We dropped our old mvc-mini-profiler and installed the MiniProfiler.EF. Then Enable-Migrations worked as expected.

Другие советы

EF Code First has an extensible provider model for Sql code generation. The documentation for DbMigrationsConfiguration.GetSqlGenerator says what it does:

Gets the SQL generator that is set to be used with a given database provider.

The MvcMiniProfiler wraps itself around the DB provider to add profiling support. To EF it will look like you're using a MvcMiniProfiler DB and not a MSSQL DB. Unfortunately EF Code first doesn't know how to handle a MvcMiniProfiler DB.

A possible fix would be to add a SqlGenerator with the MvcMiniProfiler name that wraps the Sql Server generator.

Edit

Looks like it might be possible to just reregister the existing sql server generator for the mvc mini profiler name (if you figure out the name of it).

At http://romiller.com/2012/01/16/customizing-code-first-migrations-provider/ there is code snippet that shows how to register a provider:

public Configuration()
{
    AutomaticMigrationsEnabled = false;

    SetSqlGenerator("System.Data.SqlClient", 
        new CustomMigrationsProviders.CustomSqlServerMigrationSqlGenerator());
}

This may be a related, but different problem, but since it was Anders' post here that lead me to the solution, I figured I post this solution here as well.

The Problem:

If MiniProfiler is initialized before our Entity Framework database initialization strategies execute, the initialization fails with an error about a missing migration table.

If the Entity Framework database initialization strategies execute first, access to entities fails with a type casting exception as the MiniProfiler DbConnection is tried to be forced into a SqlConnection variable (in an internal generic).

The Cause:

When MiniProfiler initializes, it uses reflection to retrieve a collection of database providers from a private static field in System.Data.Common.DbProviderFactories. It then rewrites this list with MiniProfiler shim providers to replace the native providers. This allows MiniProfiler to intercept any calls to the database silently.

When Entity Framework initializes, it starts to compile the data models and create cached initialized databases stored in System.Data.Entity.Internal.LazyInternalContext inside some private static fields. Once these are created, queries against the DbContext use the cached models and databases which are internally typed to use the providers that existed at initialization time.

When the Entity Framework database initialization strategy runs, it needs access to the bare, native Sql provider, not the MiniProfiler shim, in order to correctly generate the SQL to create tables. But once these calls to the native provider are made, the native provider is cached into LazyInternalContext and we can no longer inject the MiniProfiler shims without runtime failures.

My Solution:

Access the private collections inside System.Data.Entity.Internal.LazyInternalContext and clear out the cached compiled models and initialized databases.

If I perform this purge between the operation of the EF database initialization strategies and the initialization of MiniProfiler, the MiniProfiler shims can then be inserted without causing later runtime failures.

Code: This code did the trick for me:

Type type = typeof(DbContext).Assembly.GetType("System.Data.Entity.Internal.LazyInternalContext");
object concurrentDictionary = (type.GetField("InitializedDatabases", BindingFlags.NonPublic | BindingFlags.Static)).GetValue(null);
var initializedDatabaseCache = (IDictionary)concurrentDictionary;
if (initializedDatabaseCache != null) initializedDatabaseCache.Clear();
object concurrentDictionary2 = (type.GetField("CachedModels", BindingFlags.NonPublic | BindingFlags.Static)).GetValue(null);
var modelsCache = (IDictionary)concurrentDictionary2;
if (modelsCache != null) modelsCache.Clear();

Warning:

It appears that the names of the internal fields in LazyInternalContext change between versions of EF, so you may need to modify this code to work with the exact version of EF that you include in your project.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top