EntityFramework codice-prima stringa di connessione personalizzata e migrazioni

Quando creo un contesto con una stringa di connessione predefinita (come letto da app.config ) il database viene creato e le migrazioni funzionano – praticamente tutto è in ordine. Mentre quando la stringa di connessione viene creata in modo programmatico (usando SqlConnectionStringBuilder ):

  • il database non viene creato quando il database non è presente (scenario A );
  • CreateDbIfNotExists() crea la versione più recente del modello di database ma i meccanismi di migrazione non vengono richiamati (scenario B ).

In A viene generata un’eccezione quando desidero accedere al database, poiché – ovviamente – non è presente. Nel database B viene creato correttamente i meccanismi di migrazione non vengono chiamati, come nel caso della stringa di connessione standard.

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

costruttore :

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

inizializzatore :

 Database.SetInitializer( new MigrateDatabaseToLatestVersion() ); 

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

Se la migrazione non funziona correttamente, provare a impostare Database.Initialize(true) in DbContext ctor.

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

Ho un problema simile con le migrazioni. E nella mia soluzione devo sempre impostare l’inizializzatore del database in Ctor, come di seguito

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

InitalizeDatabase(CustomContex context) personalizzato è necessario implementare il InitalizeDatabase(CustomContex context) , ad es.

 class CustomInitializer : IDatabaseInitializer { 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); } } } } } 

AGGIORNATO

Lui è una soluzione, con le stringhe NO Connection in app.config. Utilizza le migrazioni automatiche e 2 database utilizzando lo stesso contesto. Il vero runtime ha fornito Connection. Approccio.

APP.CONFIG (Utilizza EF 6)

 < ?xml version="1.0" encoding="utf-8"?>   

Ho riscritto il codice per renderlo il più piccolo ansible per 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()); 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()); // 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().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 { public Ef6Ctx Create() { var sqlConn = Program.GetSqlConn4DBName(Program.WhichDb.DbName); // NASTY but it works return new Ef6Ctx(sqlConn); } } public class Ef6MigConf : DbMigrationsConfiguration { 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 poco1s { get; set; } public Ef6Ctx(DbConnection dbConn) : base(dbConn, true) { } } } 

Sono stato in grado di passare da una connessione all’altra usando la seguente tecnica

1) Avere più nomi di stringhe di connessione definiti in app.config.

2) Avere un costruttore nel contesto che prende il nome della stringa di connessione

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

3) Imposta il metodo Create per il contesto e rendilo in grado di ricevere il nome della connessione (usando un po ‘di trucco)

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

4) La mia configurazione di migrazione ….

  public sealed class Configuration : DbMigrationsConfiguration { etc } 

5) Impostare una funzione per creare il contesto.

  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()); // force callback by accessing database var db = new Context(connectionStringName); var site = db.Sites.FirstOrDefault() // something to access the database return db; } 

Sono arrivato a conclusioni simili.

Abbiamo avuto una lunga discussione su questo ieri . Dai un’occhiata a questo.

Se la connessione viene invocata tramite DbContext ctor, è dove vengono visualizzati i problemi (semplificati). Siccome DbMigrator chiama in realtà il tuo costruttore ‘default empty’ – così ottieni un mix di cose. Ho avuto alcuni effetti davvero strani da esso. La mia conclusione è stata che il normale inizializzatore CreateDb... funziona, ma le migrazioni no (e persino falliscono, in alcuni casi CreateDb... errori).

Bottom line – è in qualche modo creare una connessione ‘singleton’ – tramite DbContext Factory come @kirsten usato – oppure effettuare e modificare una connessione statica all’interno di DbContext – o simili. Non sono sicuro se questo risolva tutti i problemi, ma dovrebbe aiutare.

Per le migrazioni è ansible (1) utilizzare MigrateDatabaseToLatestVersion che si avvia automaticamente quando si inizia a utilizzare qualsiasi quadro nel proprio contesto o (2) utilizzare DbMigrator per indicare esplicitamente a EF di avviare la migrazione. Il vantaggio di (2) è che non è necessario eseguire un’operazione fittizia (come AddJunk nell’esempio di @ philsoady), e si potrebbe persino utilizzare MigratorScriptingDecorator se si desidera estrarre SQL di migrazione (vedere Esempio 2 nel codice)

Il trucco con (2) sembra essere quello di garantire che la stessa stringa di connessione sia utilizzata in modo coerente dalle classi DbMigrationsConfiguration e DbContext . Nota che durante il corso di DbMigration.Update vengono istanziati più contesti, ognuno dei quali chiama il costruttore predefinito del contesto (quindi fai attenzione se hai più di un costruttore). Hai anche 2 opzioni qui – puoi usare un connection string name nell’app.config (ma non puoi definire a livello di programmazione la stringa di connessione) o compilare \ hardcode \ load ecc … una connection string completa. Vedi i commenti nel codice qui sotto.

Testato in EF 6.0.1 e 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 { 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 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"); } } } 

Guarda questo link: ti dà più libertà di triggersre personalmente le migrazioni per ogni database.

Ho risolto questo problema utilizzando una stringa di connessione statica a un database specifico, all’interno del costruttore predefinito.

Diciamo che ho diversi database, tutti basati sullo stesso schema: myCatalog1, myCatalog2 ecc. Io uso solo la prima stringa di connessione al database nel costruttore in questo modo:

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

Questo costruttore viene utilizzato solo per il comando Add-Migration per funzionare e creare le migrazioni. Nota che non ci sono effetti collaterali per il resto dei database e se hai bisogno di un altro costruttore per inizializzare il contesto (per altri scopi eccetto per le migrazioni), funzionerà.

Dopo aver eseguito Add-Migration questo modo:

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

Posso chiamare il prossimo codice ( tratto dal collegamento fornito all’inizio ) per aggiornare le migrazioni a ciascuno dei miei database che si basano sullo stesso schema di 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(); } 

Volevo eseguire la migrazione automatica durante l’esecuzione in DEBUG per rendere più semplice per gli sviluppatori (il programma di installazione di produzione esegue normalmente le migrazioni) ma ha avuto lo stesso problema, una stringa di connessione specificata dal codice viene ignorata durante la migrazione.

Il mio approccio è stato quello di ricavare i contesti di migrazione da questo generico che gestisce “salvare” la stringa di connessione:

 public class MigrateInitializeContext : DbContext where TDbContext : DbContext where TMigrationsConfiguration : DbMigrationsConfiguration, new() { // ReSharper disable once StaticFieldInGenericType private static string nameOrConnectionString = typeof(TDbContext).Name; static MigrateInitializeContext() { Database.SetInitializer(new MigrateDatabaseToLatestVersion()); } protected MigrateInitializeContext(string nameOrConnectionString) : base(nameOrConnectionString) { MigrateInitializeContext.nameOrConnectionString = nameOrConnectionString; } protected MigrateInitializeContext() : base(nameOrConnectionString) { } } 

L’avvertimento di ReSharper è perché i campi statici in una class generica sono solo statici per tipo di calcestruzzo che nel nostro caso è esattamente ciò che vogliamo.

I contesti sono definiti come:

 public class MyContext : MigrateInitializeContext { public MyContext() { } public MyContext(string nameOrConnectionString) : base(nameOrConnectionString) { } public virtual DbSet MyTypes { get; set; } } 

che può essere usato normalmente.