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
):
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
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
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) { }
}
}
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;
}
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();
}
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.
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.
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");
}
}
}