EntityFramework代码优先自定义连接字符串和迁移

时间:2013-03-19 16:02:13

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

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

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

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

的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,DB:SQL Server Express 2008

7 个答案:

答案 0 :(得分:19)

如果您的迁移无法正常运行,请尝试在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);
                }
            }
        }
    }
}

<强>已更新

答案 1 :(得分:15)

他是一个解决方案,app.config中的 NO 连接字符串。 使用相同的上下文使用自动迁移和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>

我重写了代码,使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) { }
}
}

答案 2 :(得分:3)

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

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

答案 3 :(得分:1)

我得出了类似的结论。

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

如果通过DbContext ctor调用连接 - 则出现问题(简化)。由于DbMigrator实际上调用了你的'默认空'构造函数 - 所以你可以得到各种各样的东西。我有一些非常奇怪的效果。我的结论是普通初始化器CreateDb...有效 - 但迁移不起作用(甚至失败,在某些情况下会抛出错误)。

  

底线 - 是以某种方式建立'单身'连接 - 或者   通过DbContext工厂作为@kirsten使用 - 或制作和   更改DbContext中的静态连接 - 或类似的。不   确定如果能解决所有问题,但应该有所帮助。

答案 4 :(得分:1)

看看这个链接: 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(); 
}

答案 5 :(得分:0)

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

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

在EF 6.0.1&amp ;;中测试6.0.2

connection string

答案 6 :(得分:0)

我希望在DEBUG中运行时自动迁移以使开发人员更容易(生产安装程序正常进行迁移)但遇到同样的问题,迁移时会忽略代码指定的连接字符串。

我的方法是从这个通用程序中导出迁移上下文,它处理&#34; save&#34;连接字符串:

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

可以正常使用。