web-dev-qa-db-ja.com

EntityFrameworkコードファーストのカスタム接続文字列と移行

デフォルトの接続文字列(_app.config_から読み取られる)を使用してコンテキストを作成すると、データベースが作成され、移行が機能します。基本的にすべてが正常に動作しています。一方、接続文字列がプログラムで作成される場合(SqlConnectionStringBuilderを使用):

  • データベースが存在しない場合、データベースは作成されません(シナリオA)。
  • CreateDbIfNotExists()は、データベースモデルのnewestバージョンを作成しますが、移行メカニズムはnot呼び出し(シナリオ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
    >()
);
_

仕様:Entity Framework:5.0、DB:SQL Server Express 2008

23
Red XIII

移行が正しく機能しない場合は、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);
                }
            }
        }
    }
}
_

[〜#〜] updated [〜#〜]

19
rraszewski

彼は[〜#〜] no [〜#〜] app.configの接続文字列を使用したソリューションです。同じコンテキストを使用して自動移行と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) { }
}
}
15
phil soady

次の手法を使用して接続を切り替えることができました

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
Kirsten Greed

このリンクを見てください: データベースごとに移行を自分でアクティブにする自由度が増します。

デフォルトのコンストラクター内で、特定のデータベースへの静的な接続文字列を使用して、これを解決しました。

いくつかのデータベースがあり、すべてが同じスキーマに基づいているとしましょう: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"

次のコード( 最初に提供されたリンクから取得 )を呼び出して、各データベースへの移行を更新することができます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(); 
}
1
ilans

同様の結論に達しました。

昨日、長い議論がありました 。それを見てください。

DbContext ctorを介して接続が呼び出された場合-問題が発生する場所です(簡略化)。 DbMigratorは実際に 'default empty'コンストラクターを呼び出すため、さまざまなものを取得できます。私はそれからいくつかの本当に奇妙な効果がありました。私の結論は、通常の初期化子CreateDb...は動作しますが、移行は動作しません(場合によっては失敗し、エラーがスローされます)。

ボトムライン-何らかの形で「シングルトン」接続を作成することです-@kirstenが使用されているDbContext Factoryを使用するか、DbContext内で静的接続を作成および変更するか、または同様です。それですべての問題が解決するかどうかはわかりませんが、役立つはずです。

1
NSGaga

開発者が簡単に実行できるように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の警告は、ジェネリッククラスの静的フィールドが静的であるためです具象型ごとこれは、この場合exactly欲しいです。

コンテキストは次のように定義されます:

public class MyContext : MigrateInitializeContext<MyContext, Migrations.Configuration>
{
    public MyContext()
    {
    }

    public MyContext(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
    }

    public virtual DbSet<MyType> MyTypes { get; set; }
}

通常使用できます。

0
PeteB

移行の場合、(1)コンテキスト内のエンティティの使用を開始すると自動的に起動するMigrateDatabaseToLatestVersionを使用するか、(2)DbMigratorを使用して明示的にEFにキックオフするよう指示します移行。 (2)の利点は、ダミー操作(@philsoadyの例のAddJunkなど)を実行する必要がないことです。移行を抽出する場合は、MigratorScriptingDecoratorを使用することもできます。 SQL(コードの例2を参照)

(2)のトリックは、DbMigrationsConfigurationクラスとDbContextクラスで同じ接続文字列が一貫して使用されるようにすることです。 DbMigration.Updateの過程で複数のコンテキストがインスタンス化されることに注意してください。これらはすべて、コンテキストのデフォルトコンストラクターを呼び出します(複数のコンストラクターがある場合は注意してください)。ここには2つのオプションがあります-app.configでconnection string nameを使用できます(ただし、プログラムで接続文字列を定義することはできません)またはbuild\hardcode\loadなど...完全なconnection string。以下のコードのコメントを参照してください。

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