Code entityfram framework-première chaîne de connexion personnalisée et migrations

quand je crée un contexte avec une chaîne de connexion par défaut (telle que lue à partir du app.config ) la base de données est créée et les migrations fonctionnent - essentiellement tout est en ordre. Alors que lorsque la chaîne de connexion est créée programmatiquement (en utilisant SqlConnectionStringBuilder ):

  • la base de données n'est pas créée lorsque la base de données n'est pas présente (scénario A );
  • CreateDbIfNotExists() crée la plus récente version de base de données, mais les mécanismes de migration sont et non invoqués (scénario B ).

Dans A une exception est levée lorsque je souhaite accéder à la base de données, - de toute évidence - il n'y est pas. Dans B la base de données est créée correctement les mécanismes de migration sont pas appelé, comme c'est le cas dans la chaîne de connexion standard.

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

constructeur :

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

initialiseur :

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

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

22
demandé sur Kirsten Greed 2013-03-19 20:02:12

7 réponses

si votre migration ne fonctionne pas correctement, essayez de définir Database.Initialize(true) dans DbContext ctor.

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

j'ai le même problème avec les migrations. Et dans ma solution je dois toujours mettre initialiseur de base de données dans ctor, comme ci-dessous

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

dans custom initializer vous devez mettre en œuvre la méthode InitalizeDatabase(CustomContex context) , par exemple.

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

mise à JOUR

19
répondu rraszewski 2014-02-12 15:46:19

Il est une solution, avec NON les chaînes de Connexion dans l'application.config. Utilise des migrations automatiques et 2 bases de données en utilisant le même contexte. Le réel d'exécution fourni de Connexion. Approche.

APP.CONFIG (utilise 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>

j'ai réécrit le code pour faire aussi petit que possible pour 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
répondu phil soady 2013-04-21 16:00:48

j'ai été capable de passer d'une connexion à l'autre en utilisant la technique suivante

1) ont plusieurs noms de chaîne de connexion définis dans app.config.

2) Avoir un constructeur dans le contexte qui prend le nom de chaîne de connexion

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

    }

3) Configurer la méthode Create pour le contexte - et la rendre capable de recevoir le nom de connexion (en utilisant un peu d'un truc)

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

4) Ma configuration de migration ....

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

5) Configurer une fonction pour créer le contexte.

 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
répondu Kirsten Greed 2013-04-06 06:35:07

j'en suis arrivé à la même conclusion.

nous avons eu une longue discussion sur ce hier . Prendre un coup d'oeil.

si la connexion est invoquée via le ctor DbContext - c'est là que les problèmes apparaissent (simplifié). Comme DbMigrator appelle en fait votre constructeur 'default empty' - donc vous obtenez un mélange de choses. J'ai eu quelques vraiment étranges effets. Ma conclusion était que l'initialiseur normal CreateDb... fonctionne-mais les migrations ne le font pas (et même échouent, provoquent des erreurs dans certains cas).

, C'est - à-dire établir un lien "unique" grâce à L'usine DbContext comme @ kirsten utilisé-ou la fabrication et changer une connexion statique dans votre DbContext - ou similaire. Pas bien sûr si cela résout tous les problèmes, mais devrait aider.

1
répondu NSGaga 2017-05-23 11:54:44

pour les migrations, vous pouvez soit (1) utiliser MigrateDatabaseToLatestVersion qui se déclenchera automatiquement lorsque vous commencez à utiliser l'une des entités dans votre contexte ou (2) utiliser DbMigrator pour dire explicitement à EF de lancer la migration. L'avantage de (2) est que vous n'avez pas à effectuer une opération factice (comme AddJunk dans l'exemple de @philsoady), et vous pouvez même utiliser MigratorScriptingDecorator si vous voulez extraire le SQL de migration (voir L'exemple 2 dans le code)

Le truc avec (2) semble être en s'assurant que la même chaîne de connexion est utilisée de manière cohérente par vos classes DbMigrationsConfiguration et DbContext . Notez que plusieurs contextes sont instanciés au cours de DbMigration.Update - qui appellent tous le constructeur par défaut du contexte (faites donc attention si vous avez plus d'un constructeur). Vous avez aussi 2 options ici - vous pouvez utiliser un connection string name dans l'application.config (mais alors vous ne pouvez pas définir programmatiquement la chaîne de connexion) ou build\hardcode\load etc... un compléter connection string . Voir les commentaires dans le code ci-dessous.

testé dans L'essai 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
répondu Ilan 2014-02-20 22:48:23

Regardez ce lien: il vous donne plus de liberté pour activer vous-même les migrations pour chaque base de données.

j'ai résolu cela en utilisant une chaîne de connexion statique à une base de données spécifique, à l'intérieur du constructeur par défaut.

disons que j'ai plusieurs bases de données, toutes sont basées sur le même schéma: myCatalog1, myCatalog2 etc. J'utilise seulement la première chaîne de connexion de base de données dans le constructeur comme ceci:

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

ce constructeur est utilisé uniquement pour la commande Add-Migration pour travailler et créer les migrations. Notez qu'il n'y a pas d'effets secondaires pour le reste des bases de données et que si vous avez besoin d'un autre constructeur pour initialiser le contexte (pour d'autres raisons sauf pour les migrations), il fonctionnera.

après avoir lancé le Add-Migration comme ceci:

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

je peux appeler le code suivant ( tiré du lien fourni à la début ) afin de mettre à jour les migrations vers chacune de mes bases de données qui sont basées sur le même schéma que 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
répondu ilans 2017-05-23 12:34:35

j'ai voulu migrer automatiquement lors de L'exécution du débogage pour le rendre facile pour le devs (l'installateur de production fait les migrations normalement) mais avait le même problème, une chaîne de connexion spécifiée par code est ignorée lors de la migration.

mon approche était de dériver les contextes de migration à partir de ce générique qui gère la "sauvegarde" de la chaîne de connexion:

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

l'avertissement ReSharper est parce que les champs statiques dans une classe générique sont seulement statique par type de béton qui dans notre cas est exactement ce que nous voulons.

Les contextes

sont définis comme suit:

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

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

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

qui peut être utilisé normalement.

0
répondu PeteB 2015-07-27 09:33:45