EntityFramework代码优先自定义连接string和迁移

当我使用默认的连接string(从app.config读取)创build一个上下文时,数据库被创build并且迁移工作 – 基本上所有东西都是按顺序的。 而当以编程方式创build连接string(使用SqlConnectionStringBuilder )时:

  • 当数据库不存在时,数据库不会被创build(schemeA );
  • CreateDbIfNotExists()创build最新版本的数据库模型,但不会调用迁移机制(schemeB )。

A ,当我想访问数据库时抛出一个exception,显然,它不在那里。 在B数据库中创build正确的迁移机制不会被调用,就像标准连接string中的情况一样。

app.config :“ Data Source=localhost\\SQLEXPRESS;Initial Catalog=Db13;User ID=xxx;Password=xxx

build设者

 sqlBuilder.DataSource = x.DbHost; sqlBuilder.InitialCatalog = x.DbName; sqlBuilder.UserID = x.DbUser; sqlBuilder.Password = x.DbPassword; 

初始值设定项

 Database.SetInitializer( new MigrateDatabaseToLatestVersion< MyContext, Migrations.Configuration >() ); 

规格 :entity framework:5.0,DB:SQL Server Express 2008

如果您的迁移无法正常工作,请尝试在DbContext ctor中设置Database.Initialize(true)

 public CustomContext(DbConnection connection) : base(connection, true) { Database.Initialize(true); } 

我有类似的迁移问题。 在我的解决scheme中,我必须始终设置数据库初始值设定器,如下所示

 public CustomContext(DbConnection connection) : base(connection, true) { Database.SetInitializer(new CustomInitializer()); Database.Initialize(true); } 

在自定义初始化程序中,您必须实现InitalizeDatabase(CustomContex context)方法,例如。

 class CustomInitializer : IDatabaseInitializer<CustomContext> { public void InitializeDatabase(CustomContext context) { if (!context.Database.Exists || !context.Database.CompatibleWithModel(false)) { var configuration = new Configuration(); var migrator = new DbMigrator(configuration); migrator.Configuration.TargetDatabase = new DbConnectionInfo(context.Database.Connection.ConnectionString, "System.Data.SqlClient"); var migrations = migrator.GetPendingMigrations(); if (migrations.Any()) { var scriptor = new MigratorScriptingDecorator(migrator); string script = scriptor.ScriptUpdate(null, migrations.Last()); if (!String.IsNullOrEmpty(script)) { context.Database.ExecuteSqlCommand(script); } } } } } 

更新

他是一个解决scheme,在app.config中没有连接string。 使用相同的上下文使用自动迁移和2个数据库。 实际运行时提供了Connection。 方法。

APP.CONFIG(使用EF 6)

 <?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"> <parameters> <parameter value="Data Source=localhost; Integrated Security=True; MultipleActiveResultSets=True" /> </parameters> </defaultConnectionFactory> </entityFramework> </configuration> 

我重写了代码,以尽可能小的演示:

 using System; using System.Data.Common; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Entity.Migrations; namespace Ef6Test { public class Program { public static void Main(string[] args) { Database.SetInitializer(new MigrateDatabaseToLatestVersion<Ef6Ctx, Ef6MigConf>()); WhichDb.DbName = "HACKDB1"; var sqlConn = GetSqlConn4DBName(WhichDb.DbName); var context = new Ef6Ctx(sqlConn); context.Database.Initialize(true); AddJunk(context); //sqlConn.Close(); //?? whatever other considerations, dispose of context etc... Database.SetInitializer(new MigrateDatabaseToLatestVersion<Ef6Ctx, Ef6MigConf>()); // yes its default again reset this !!!! WhichDb.DbName = "HACKDB2"; var sqlConn2 = GetSqlConn4DBName(WhichDb.DbName); var context2 = new Ef6Ctx(sqlConn2); context2.Database.Initialize(true); AddJunk(context2); } public static class WhichDb { // used during migration to know which connection to build public static string DbName { get; set; } } private static void AddJunk(DbContext context) { var poco = new pocotest(); poco.f1 = DateTime.Now.ToString(); // poco.f2 = "Did somebody step on a duck?"; //comment in for second run context.Set<pocotest>().Add(poco); context.SaveChanges(); } public static DbConnection GetSqlConn4DBName(string dbName) { var sqlConnFact = new SqlConnectionFactory( "Data Source=localhost; Integrated Security=True; MultipleActiveResultSets=True"); var sqlConn = sqlConnFact.CreateConnection(dbName); return sqlConn; } } public class MigrationsContextFactory : IDbContextFactory<Ef6Ctx> { public Ef6Ctx Create() { var sqlConn = Program.GetSqlConn4DBName(Program.WhichDb.DbName); // NASTY but it works return new Ef6Ctx(sqlConn); } } public class Ef6MigConf : DbMigrationsConfiguration<Ef6Ctx> { public Ef6MigConf() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } } public class pocotest { public int Id { get; set; } public string f1 { get; set; } // public string f2 { get; set; } // comment in for second run } public class Ef6Ctx : DbContext { public DbSet<pocotest> poco1s { get; set; } public Ef6Ctx(DbConnection dbConn) : base(dbConn, true) { } } } 

我已经能够使用以下技术在连接之间切换

1)在app.config中定义了多个连接string名称。

2)在获取连接string名称的上下文中有一个构造函数

 public Context(string connStringName) : base(connStringName) { } 

3)为上下文设置Create方法 – 并使其能够接收连接名称(使用一些技巧)

  public class ContextFactory : IDbContextFactory<Context> { public Context Create() { var s = (string)AppDomain.CurrentDomain.GetData("ConnectionStringName"); var context = new Context(s); return context; } } 

4)我的迁移configuration….

  public sealed class Configuration : DbMigrationsConfiguration<SBD.Syrius.DataLayer.Context> { etc } 

5)设置一个函数来创build上下文。

  private static Context MyCreateContext(string connectionStringName ) { // so that we can get the connection string name to the context create method AppDomain.CurrentDomain.SetData("ConnectionStringName", connectionStringName); // hook up the Migrations configuration Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context, Configuration>()); // force callback by accessing database var db = new Context(connectionStringName); var site = db.Sites.FirstOrDefault() // something to access the database return db; } 

我得出了类似的结论。

我们昨天就这个问题进行了冗长的讨论 。 看看它。

如果连接是通过DbContext ctor调用的 – 这就是出现问题(简化)的地方。 因为DbMigrator实际上调用了你的“默认空”构造函数 – 所以你得到了一个混合的东西。 我有一些奇怪的效果。 我的结论是,正常的初始化CreateDb...工作 – 但迁移不(甚至失败,在某些情况下抛出错误)。

底线 – 是以某种方式做一个“单身”连接 – 无论是通过DbContext工厂作为@kirsten使用 – 或者在DbContext中创build和更改静态连接 – 或类似的。 不知道是否解决所有问题,但应该帮助。

对于迁移,您可以(1)使用MigrateDatabaseToLatestVersion ,当您开始使用上下文中的任何实体时,将自动启动DbMigrator ,或者(2)使用DbMigrator明确告知EF启动迁移。 (2)的优点是你不必执行一个虚拟操作(如@ philsoady例子中的AddJunk),如果你想提取迁移SQL,你甚至可以使用MigratorScriptingDecorator (参见代码示例2)

(2)的技巧似乎是在确保您的DbMigrationsConfigurationDbContext类一致地使用相同的连接string。 请注意,在DbMigration.Update过程中会实例化多个上下文 – 所有这些都会调用上下文的默认构造函数(因此请注意,如果您有多个构造函数)。 在这里你也有2个选项 – 你可以在app.config中使用connection string name (但是不能以编程的方式定义连接string)或者build立\ hardcode \ load等…一个完整的connection string 。 请参阅下面代码中的注释。

在EF 6.0.1和6.0.2中testing

 using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Entity.Migrations; using System.Data.Entity.Migrations.Infrastructure; namespace ConsoleApplication1 { // Models public class Foo { [Key] public int Id { get; set; } public string Column1 { get; set; } public string Column2 { get; set; } } // Configuration public class Configuration : DbMigrationsConfiguration<Context> { public static string StaticConnectionString; // use connection string public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; TargetDatabase = new DbConnectionInfo(StaticConnectionString, "System.Data.SqlClient"); // use connection string //TargetDatabase = new DbConnectionInfo("ConnectionStringName"); // use connection string name in app.config } protected override void Seed(Context context) { } } // Context public class Context : DbContext { public Context() //: base("ConnectionStringName") // use connection string name in app.config : base(ConsoleApplication1.Configuration.StaticConnectionString) // use connection string { } public IDbSet<Foo> Foos { get; set; } } // App class Program { static void Main(string[] args) { // Example 1 - migrate to test1 DB Configuration.StaticConnectionString = "Data Source=localhost;Initial Catalog=test1;Integrated Security=True;MultipleActiveResultSets=True"; var configuration = new Configuration(); var migrator = new DbMigrator(configuration); migrator.Update(); Console.WriteLine("Migration 1 complete"); // Example 2 - create migrate SQL and migrate to test2 DB // NOTE: You can't do this if you use a connection string name in app.config // Generate migrate sql script for migration to test2 DB Configuration.StaticConnectionString = "Data Source=localhost;Initial Catalog=test2;Integrated Security=True;MultipleActiveResultSets=True"; configuration = new Configuration(); migrator = new DbMigrator(configuration); var scriptor = new MigratorScriptingDecorator(migrator); string sql = scriptor.ScriptUpdate(null, null); Console.WriteLine("Migration 2 SQL:\n" + sql); // Perform migration to test2 DB configuration = new Configuration(); migrator = new DbMigrator(configuration); migrator.Update(); Console.WriteLine("Migration 2 complete"); } } } 

看看这个链接: 它给你更多的自由来激活你自己对每个数据库的迁移。

我解决了这个问题,通过使用静态连接string到默认构造函数内的特定数据库。

比方说,我有几个数据库,都基于相同的架构:myCatalog1,myCatalog2等我只使用第一个数据库连接string在这样的构造函数:

 public MyContext() : base("Data Source=.\SQLEXPRESS;Initial Catalog=myCatalog1;Integrated Security=True") { // Can leave the rest of the constructor function itself empty } 

此构造函数仅用于“ Add-Migration命令以工作并创build迁移。 请注意,其他数据库没有副作用,如果您需要另一个构造函数来初始化上下文(用于迁移除外的其他目的),它将起作用。

在我像这样运行Add-Migration

 Add-Migration -ConfigurationTypeName YourAppName.YourNamespace.Configuration "MigrationName" 

我可以调用下一个代码( 取自开始提供的链接 ),以便更新迁移基于与myCatalog1相同模式的每个数据库的迁移:

 YourMigrationsConfiguration cfg = new YourMigrationsConfiguration(); cfg.TargetDatabase = new DbConnectionInfo( theConnectionString, "provider" ); DbMigrator dbMigrator = new DbMigrator( cfg ); if ( dbMigrator.GetPendingMigrations().Count() > 0 ) { // there are pending migrations // do whatever you want, for example dbMigrator.Update(); } 

我想在DEBUG中运行时自动进行迁移,以便开发人员(生产安装程序正常进行迁移)有相同的问题,迁移时会忽略代码指定的连接string。

我的方法是从这个处理“保存”连接string的generics中派生出迁移的上下文:

 public class MigrateInitializeContext<TDbContext, TMigrationsConfiguration> : DbContext where TDbContext : DbContext where TMigrationsConfiguration : DbMigrationsConfiguration<TDbContext>, new() { // ReSharper disable once StaticFieldInGenericType private static string nameOrConnectionString = typeof(TDbContext).Name; static MigrateInitializeContext() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<TDbContext, TMigrationsConfiguration>()); } protected MigrateInitializeContext(string nameOrConnectionString) : base(nameOrConnectionString) { MigrateInitializeContext<TDbContext,TMigrationsConfiguration>.nameOrConnectionString = nameOrConnectionString; } protected MigrateInitializeContext() : base(nameOrConnectionString) { } } 

ReSharper警告是因为generics类中的静态字段只是静态的每个具体types ,在我们的情况下, 正是我们想要的。

上下文被定义为:

 public class MyContext : MigrateInitializeContext<MyContext, Migrations.Configuration> { public MyContext() { } public MyContext(string nameOrConnectionString) : base(nameOrConnectionString) { } public virtual DbSet<MyType> MyTypes { get; set; } } 

可以正常使用。