web-dev-qa-db-fra.com

EntityFramework, chaîne de connexion personnalisée en premier code et migrations

Lorsque je crée un contexte avec une chaîne de connexion par défaut (telle que lue dans app.config), La base de données est créée et les migrations fonctionnent - en gros, tout est en ordre. Alors que lorsque la chaîne de connexion est créée par programme (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 version la plus récente du modèle de base de données mais les mécanismes de migration ne sont pas invoqué (scénario B).

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

app.config: "Data Source=localhost\\SQLEXPRESS;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
    >()
);

Spécifications: Entity Framework: 5.0, DB: SQL Server Express 2008

23
Red XIII

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

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

J'ai un problème similaire avec les migrations. Et dans ma solution, je dois toujours définir l'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 l'initialiseur personnalisé, vous devez implémenter 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);
                }
            }
        }
    }
}

MIS À JOUR

19
rraszewski

C'est une solution, avec PAS DE Chaînes de connexion dans app.config. Utilise des migrations automatiques et 2 bases de données utilisant le même contexte. La véritable connexion fournie par le runtime. 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 le rendre le plus petit possible pour la démo:

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

J'ai pu basculer entre les connexions en utilisant la technique suivante

1) Plusieurs noms de chaînes de connexion sont définis dans app.config.

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

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

    }

3) Configurez la méthode Create pour le contexte - et rendez-la capable de recevoir le nom de la connexion (en utilisant un peu d'une astuce)

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

Regardez ce lien: Cela 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 uniquement la première chaîne de connexion à la 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 le Add-Migration commande 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 si vous avez besoin d'un autre constructeur pour initialiser le contexte (à d'autres fins que pour les migrations), cela fonctionnera.

Après avoir exécuté le Add-Migration comme ça:

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

Je peux appeler le code suivant ( tiré du lien fourni au début ) afin de mettre à jour les migrations vers chacune de mes bases de données qui sont basés 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(); 
}
1
ilans

Je suis arrivé à des conclusions similaires.

Nous avons eu une longue discussion à ce sujet hier . Jetez un coup d'oeil.

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

En bout de ligne - est de faire en quelque sorte une connexion 'singleton' - soit via DbContext Factory comme @kirsten utilisé - ou d'établir et de modifier une connexion statique dans votre DbContext - ou similaire. Je ne sais pas si cela résout tous les problèmes, mais cela devrait aider.

1
NSGaga

Je voulais migrer automatiquement lors de l'exécution dans DEBUG pour faciliter la tâche des développeurs (le programme d'installation de production effectue les migrations normalement) mais j'ai eu le même problème, une chaîne de connexion spécifiée par code est ignorée lors de la migration.

Mon approche a été de dériver les contextes de migration 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 dû au fait que les champs statiques d'une classe générique sont uniquement statiques par type concret qui dans notre cas est exactement ce que nous vouloir.

Les contextes sont définis comme:

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
PeteB

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

L'astuce avec (2) semble être de s'assurer 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 (alors faites attention si vous avez plus d'un constructeur). Vous avez également 2 options ici - vous pouvez utiliser un connection string name Dans le fichier app.config (mais alors vous ne pouvez pas définir par programmation la chaîne de connexion) ou construire\hardcode\load etc ... un connection string. Voir les commentaires dans le code ci-dessous.

Testé dans 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