简体   繁体   中英

Get local migrations from assembly using EF Code First without a connection string

So I'm stuck with the following situation, maybe you guys can shed some light on this:

Scenario
Our application exists of many client applications (Winforms), each of which use a specific version of the database model using EF Code First Migrations. The client application has the ability to connect to several different databases, which all support the required schema the client needs.

Next we have one central service application. This application is responsible for maintaining databases (creating/removing), and returning connection strings to clients who need access. The client can send a request to this central service to create a new database. One of the arguments of this call is the schema version it needs (as a string). This is where the problem lies:

Problem
The problem is to find out what the highest version of migration is, without providing any connection string or database to connect to. Normally one would use:

var configuration = new MigrationsConfiguration() { ContextType = typeof(CoreContext) };
var migrator = new DbMigrator(configuration);
var versions = migrator.GetLocalMigrations();

but this automatically results into connecting to a database under the hood (if not provided, just .\\SQLExpress). This behavior happens even when you explicitly set the DatabaseInitializer to null and provide a DbMigrationsConfiguration implementation with AutomaticMigrations set to false.

Trying to follow the stacktrace I see the following:

at System.Data.Entity.Utilities.DbProviderServicesExtensions.GetProviderManifestTokenChecked(DbProviderServices providerServices, DbConnection connection)
at System.Data.Entity.Infrastructure.DefaultManifestTokenResolver.<>c__DisplayClass1.<ResolveManifestToken>b__0(Tuple`3 k)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at System.Data.Entity.Infrastructure.DefaultManifestTokenResolver.ResolveManifestToken(DbConnection connection)
at System.Data.Entity.Utilities.DbConnectionExtensions.GetProviderInfo(DbConnection connection, DbProviderManifest& providerManifest)
at System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection)
at System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext)
at System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input)
at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
at System.Data.Entity.Internal.LazyInternalContext.get_ModelBeingInitialized()
at System.Data.Entity.Infrastructure.EdmxWriter.WriteEdmx(DbContext context, XmlWriter writer)
at System.Data.Entity.Utilities.DbContextExtensions.<>c__DisplayClass1.<GetModel>b__0(XmlWriter w)
at System.Data.Entity.Utilities.DbContextExtensions.GetModel(Action`1 writeXml)
at System.Data.Entity.Utilities.DbContextExtensions.GetModel(DbContext context)
at System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration, DbContext usersContext)
at System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration)

This gets a little too much rocket science-like for me. Maybe somebody can explain to me whether it's possible or not to work with EF Code First Models without providing a connection string or database. Or maybe I'm missing a crucial configuration setting?

Regards, Patrick

ps. I currently found a workaround by using reflection of the assembly/namespace in which the migrations resides, and selecting their Id's. That is actually how GetLocalMigrations() actually works.

Well, you mostly answered your own question but to confirm your suspicion: you cannot get the local migraitons with the DbMigrator class without providing the target database.

Regarding your solution with using Reflection and you are right the original approach uses this as well in the MigrationAssembly class, for a simplified version you can use a code like this:

var migrationsSimplified = (from t in migrationsAssembly.DefinedTypes.Select(t => t.AsType())
                            where t.IsSubclassOf(typeof(DbMigration))
                            select (IMigrationMetadata)Activator.CreateInstance(t))
                            .OrderBy(mm => mm.Id)
                            .ToList();

The original function can be found here: http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Migrations/Infrastructure/MigrationAssembly.cs

And the other part of the question, if you go backwards on the stack trace of the exception you will find the

System.Data.Entity.Utilities.DbContextExtensions.GetModel(this DbContext context)

function you will find here: http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Infrastructure/EdmxWriter.cs This will use the context whose type you specified in the DbMigrationsConfiguration.

And if you go deeper you will actually find the GetProviderInfo info in the DbConnectionExtensions class which wants to get the connection you haven't defined.

One thing to note: this exception will not occure unti you call the GetLocalMigrations functions because the context uses lazy initialization.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM