【问题标题】:EntityFramework code-first custom connection string and migrationsEntityFramework 代码优先自定义连接字符串和迁移
【发布时间】:2013-03-08 10:06:19
【问题描述】:

当我使用默认连接字符串(从app.config 读取)创建上下文时,会创建数据库并且迁移工作 - 基本上一切都井井有条。而当以编程方式创建连接字符串时(使用SqlConnectionStringBuilder):

  • 数据库不存在时不会创建数据库(场景A);
  • CreateDbIfNotExists() 创建最新 版本的数据库模型,但没有调用迁移机制(场景B)。

A 中,当我希望访问数据库时会引发异常,因为 - 显然 - 它不存在。在B 中,数据库被正确创建,迁移机制被调用,就像标准连接字符串中的情况一样。

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

建造者

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

初始化器

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

规格: 实体框架:5.0,数据库:SQL Server Express 2008

【问题讨论】:

标签: entity-framework ef-code-first connection-string entity-framework-migrations


【解决方案1】:

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

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

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

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) 我的迁移配置 ....

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

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

 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;
    }

【讨论】:

  • 必须添加 ContextFactory 类才能使其工作似乎很奇怪。
  • 还有一件事——为了创建迁移,你需要放回上下文构造函数——以便在创建迁移时知道要比较的数据库
  • 在我的问题中,我无法从 app.config 中读取 connectionString,因为我的连接字符串需要动态(以编程方式)创建。我没有必须支持的静态数据库列表。如果我有一个在 app.config 中定义了 connectionStrings 的静态数据库列表,我会选择 DbContext(string nameOrConnectionString) ctor 来更改 connectionStrings。您的解决方案似乎太复杂了...
  • 我同意@Radek 的解决方案看起来更好。但我还不能让它工作 - 看看我下面的评论
  • 你看到上面的帖子了吗?我遇到了同样的问题并找到了解决方案。
【解决方案2】:

我得出了类似的结论。

We had a lengthy discussion on that yesterday。看看它。

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

底线 - 是以某种方式建立“单例”连接 - 要么 通过 @kirsten 使用的 DbContext 工厂 - 或制作和 更改 DbContext 中的静态连接 - 或类似的。不是 确定这是否可以解决所有问题,但应该会有所帮助。

【讨论】:

    【解决方案3】:

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

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

    我有类似的迁移问题。在我的解决方案中,我必须始终在 ctor 中设置数据库初始化程序,如下所示

    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);
                    }
                }
            }
        }
    }
    

    更新

    【讨论】:

    • 太好了,这似乎可以完成工作!有点解决方法,但仍然符合我的预期。
    • @Radek,你能展示一下 CustomInitializer 类继承自什么吗?
    • 公共类 CustomInitializer : IDatabaseInitializer
    • 谢谢@Radek。你也有 IDbContextFactory 的实现吗?我得到一个 MigrationsException `Context is not constrictible。添加默认构造函数或提供 IDbContextFactory 的实现
    • 不,我没有 ContextFactory。
    【解决方案4】:

    他是一个解决方案,在 app.config 中有 NO 连接字符串。 使用自动迁移和使用相同上下文的 2 个数据库。 真正的运行时提供的连接。接近。

    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>
    

    我重写了代码以使 Demo 尽可能小:

    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) { }
    }
    }
    

    【讨论】:

    • 我知道迈克的感觉太好了:-D
    • 从 EF 6 开始,您可以使用 MigrateDatabaseToLatestVersion&lt;TContext, TMigrationsConfiguration&gt;(true) 强制迁移使用触发初始化的上下文中的连接信息。我认为这与您的代码实现了相同的目标?
    • MigrateDatabaseToLatestVersion&lt;TContext, TMigrationsConfiguration&gt; 在样本中
    • 您使用了MigrateDatabaseToLatestVersion&lt;TContext, TMigrationsConfigur‌​ation&gt;,但没有使用真正的参数来重用调用上下文。
    【解决方案5】:

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

    (2) 的诀窍似乎在于确保DbMigrationsConfigurationDbContext 类始终使用相同的连接字符串。请注意,在DbMigration.Update 的过程中会实例化多个上下文 - 所有这些都会调用上下文的默认构造函数(因此请注意是否有多个构造函数)。您在这里还有 2 个选项 - 您可以在 app.config 中使用 connection string name(但您不能以编程方式定义连接字符串)或 build\hardcode\load 等...一个完整的 connection string。请参阅下面代码中的 cmets。

    在 EF 6.0.1 和 6.0.2 中测试

    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");
            }
        }
    }
    

    【讨论】:

      【解决方案6】:

      看看这个链接: It gives you more freedom to activate the migrations yourself for each database.

      我通过在默认构造函数中使用到特定数据库的静态连接字符串解决了这个问题。

      假设我有几个数据库,都基于相同的架构:myCatalog1、myCatalog2 等。 我只使用构造函数中的第一个数据库连接字符串,如下所示:

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

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

      在我像这样运行Add-Migration 之后:

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

      我可以调用下一个代码 (taken from the link provided at the beginning) 来更新迁移到我的每个数据库,它们基​​于与 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(); 
      }
      

      【讨论】:

        【解决方案7】:

        我想在 DEBUG 中运行时自动迁移以方便开发人员(生产安装程序正常执行迁移)但遇到了同样的问题,迁移时忽略了代码指定的连接字符串。

        我的方法是从这个处理“保存”连接字符串的泛型中派生迁移上下文:

        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 警告是因为泛型类中的静态字段仅是静态的每个具体类型,在我们的例子中,正是我们想要的。

        上下文定义为:

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

        可以正常使用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-09-08
          • 1970-01-01
          • 1970-01-01
          • 2015-03-04
          • 1970-01-01
          相关资源
          最近更新 更多