EntityFramework code-pierwszy niestandardowy ciąg połączeń i migracje

Kiedy tworzę kontekst z domyślnym łańcuchem połączeń (odczytanym z app.config), powstaje baza danych i migracje działają-w zasadzie wszystko jest w porządku. Natomiast gdy łańcuch połączeniowy jest tworzony programowo (za pomocą SqlConnectionStringBuilder):

  • baza danych nie jest tworzona, gdy baza danych nie jest obecna (scenariusz A);
  • CreateDbIfNotExists() tworzy najnowszą wersję modelu bazy danych, ale mechanizmy migracji są wywoływane , a nie (scenariusz B).

W A wyjątek jest wyrzucany, gdy chcę uzyskać dostęp do bazy danych, ponieważ-oczywiście-nie ma go. W B baza danych jest tworzona poprawnie mechanizmy migracji są wywoływane , a nie, Jak to ma miejsce w standardowym łańcuchu połączeń.

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

Budowniczy :

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

Inicjalizator :

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

Specs : Entity Framework: 5.0, DB: SQL Server Express 2008

Author: Kirsten Greed, 2013-03-19

7 answers

Jeśli migracja nie działa poprawnie, spróbuj ustawić Database.Initialize(true) w DbContext ctor.

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

Mam podobny problem z migracjami. A w moim rozwiązaniu muszę zawsze ustawić database initializer w ctor, jak poniżej

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

W custom initializer musisz zaimplementować metodę InitalizeDatabase(CustomContex context), np.

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

Aktualizacja

 19
Author: rraszewski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-12 15:46:19

Jest rozwiązaniem, z Bez ciągów połączeń w aplikacji.config. Używa automatycznych migracji i 2 baz danych przy użyciu tego samego kontekstu. Rzeczywistym uruchomieniu dostarczonego połączenia. Podejdźcie.

APP.CONFIG (używa 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>

Przerobiłem kod, aby zrobić jak najmniejszy dla 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) { }
}
}
 14
Author: phil soady,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-21 16:00:48

Udało mi się przełączać między połączeniami przy użyciu następującej techniki

1) posiada wiele nazw łańcuchów połączeń zdefiniowanych w aplikacji.config.

2) mieć konstruktor w kontekście, który przyjmuje nazwę Łańcucha połączeń

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

    }

3) Skonfiguruj metodę Create dla kontekstu-i spraw, aby była w stanie odbierać nazwę połączenia ( używając pewnej sztuczki)

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

4) moja konfiguracja migracji ....

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

5) ustaw funkcję do tworzenia kontekst.

 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
Author: Kirsten Greed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-06 06:35:07

Doszedłem do podobnych wniosków.

Wczoraj odbyliśmy długą dyskusję na ten temat. Spójrz na to.

Jeśli połączenie jest wywoływane przez DbContext ctor - to tam pojawiają się problemy (uproszczone). Ponieważ DbMigrator faktycznie nazywa Twój' domyślny pusty ' konstruktor - więc otrzymujesz mieszankę rzeczy. Miałem z tego naprawdę dziwne efekty. Mój wniosek był taki, że normalny inicjalizator CreateDb... działa - ale migracje nie (a nawet zawodzą, w niektórych przypadkach rzucają błędy).

Reasumując-to jakoś nawiązać połączenie "singleton" - albo przez Fabrykę DbContext jako @ kirsten-lub tworząc i zmiana połączenia statycznego w DbContext - lub podobnego. Nie jasne, jeśli to rozwiąże wszystkie problemy, ale powinno pomóc.

 1
Author: NSGaga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:44

W przypadku migracji możesz albo (1) użyć MigrateDatabaseToLatestVersion, który uruchomi się automatycznie, gdy zaczniesz używać dowolnego elementu w Twoim kontekście lub (2) użyć DbMigrator, aby wyraźnie powiedzieć EF, aby rozpoczął migrację. Zaletą (2) jest to, że nie musisz wykonywać operacji atrapy (jak AddJunk w przykładzie @philsoady), a możesz nawet użyć MigratorScriptingDecorator, Jeśli chcesz wyodrębnić migracyjny SQL (patrz przykład 2 w kodzie)

Sztuczka z (2) wydaje się być w zapewnieniu, że ten sam łańcuch połączeń jest używany konsekwentnie przez klasy DbMigrationsConfiguration i DbContext. Zauważ, że w trakcie DbMigration.Update powstaje wiele kontekstów-wszystkie wywołują domyślny konstruktor kontekstu (więc uważaj, jeśli masz więcej niż jeden konstruktor). Masz tu również 2 opcje-możesz użyć connection string name w aplikacji.config (ale wtedy nie można programowo zdefiniować łańcucha połączeń) lub build \ hardcode\load itp... kompletny connection string. Zobacz komentarze w kodzie poniżej.

Testowane w 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
Author: Ilan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-20 22:48:23

Spójrz na ten link: daje Ci większą swobodę samodzielnego aktywowania migracji dla każdej bazy danych.

Rozwiązałem to używając statycznego łańcucha połączeń do określonej bazy danych, wewnątrz domyślnego konstruktora.

Załóżmy, że mam kilka baz danych, wszystkie są oparte na tym samym schemacie: myCatalog1, myCatalog2 itd. Używam tylko pierwszego łańcucha połączenia z bazą danych w konstruktorze tak:

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

Ten konstruktor jest używany tylko do działania polecenia Add-Migration i utworzyć migracje. Zauważ, że nie ma żadnych skutków ubocznych dla reszty baz danych i jeśli potrzebujesz innego konstruktora do inicjalizacji kontekstu (do innych celów, z wyjątkiem migracji), będzie to działać.

Po uruchomieniu Add-Migration tak:

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

Mogę wywołać następny kod (zaczerpnięty z linku podanego na początku) w celu aktualizacji migracji do każdej z moich baz danych , które są oparte na tym samym schemacie co 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(); 
}
 0
Author: ilans,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:35

Chciałem automatycznie migrować podczas pracy w debugowaniu, aby ułatwić programistom (instalator produkcyjny wykonuje migracje normalnie), ale miałem ten sam problem, łańcuch połączeń określony kodem jest ignorowany podczas migracji.

Moje podejście polegało na wyprowadzeniu migrujących kontekstów z tego generycznego, który obsługuje "zapisywanie" ciągu połączenia:

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

Ostrzeżenie ReSharper jest dlatego, że pola statyczne w klasie ogólnej są tylko statyczne na konkretny typ, który w nasza sprawa jest dokładnie tym, czego chcemy.

Konteksty są zdefiniowane jako:

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

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

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

Które mogą być używane normalnie.

 0
Author: PeteB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-27 09:33:45