entity framework - update - EntityFramework primer código cadena de conexión personalizada y migraciones
update database entity framework code first (7)
Él es una solución, sin cadenas de conexión en app.config. Utiliza migraciones automáticas y 2 bases de datos usando el mismo contexto. El tiempo de ejecución real proporcionado Conexión. Enfoque.
APP.CONFIG (utiliza 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>
Reescribí el código para hacer lo más pequeño posible para 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) { }
}
}
Cuando creo un contexto con una cadena de conexión predeterminada (como se lee desde app.config
), se crea la base de datos y las migraciones funcionan, básicamente todo está en orden. Mientras que cuando la cadena de conexión se crea programáticamente (usando SqlConnectionStringBuilder
):
- la base de datos no se crea cuando la base de datos no está presente (escenario
A
); -
CreateDbIfNotExists()
crea la versión más reciente del modelo de base de datos, pero los mecanismos de migración no se invocan (escenarioB
).
En A
se lanza una excepción cuando deseo acceder a la base de datos, ya que, obviamente, no está allí. En la base de datos B
se crea correctamente los mecanismos de migración no se llaman, como es el caso en la cadena de conexión estándar.
app.config : " Data Source=localhost//SQLEXPRESS;Initial Catalog=Db13;User ID=xxx;Password=xxx
"
constructor
sqlBuilder.DataSource = x.DbHost;
sqlBuilder.InitialCatalog = x.DbName;
sqlBuilder.UserID = x.DbUser;
sqlBuilder.Password = x.DbPassword;
inicializador :
Database.SetInitializer(
new MigrateDatabaseToLatestVersion<
MyContext,
Migrations.Configuration
>()
);
Especificaciones : Entity Framework: 5.0, DB: SQL Server Express 2008
He llegado a conclusiones similares.
Tuvimos una larga discusión sobre eso ayer . Mira esto.
Si se invoca la conexión a través de DbContext ctor, es donde aparecen los problemas (simplificados). Como DbMigrator
realidad llama a tu constructor ''predeterminado vacío'', entonces obtienes una combinación de cosas. Tuve algunos efectos realmente extraños. Mi conclusión fue que el inicializador normal CreateDb...
funciona, pero las migraciones no (e incluso fallan, arrojan errores en algunos casos).
En pocas palabras: es de alguna manera hacer una conexión ''singleton'', ya sea a través de DbContext Factory como @kirsten utilizado, o hacer y cambiar una conexión estática dentro de su DbContext, o similar. No estoy seguro de si eso resuelve todos los problemas, pero debería ayudar.
He podido cambiar de conexión utilizando la siguiente técnica
1) Tener múltiples nombres de cadena de conexión definidos en app.config.
2) Tener un constructor en el contexto que toma el nombre de la cadena de conexión
public Context(string connStringName)
: base(connStringName)
{
}
3) Configure el método Create para el contexto y haga que sea capaz de recibir el nombre de la conexión (usando un truco)
public class ContextFactory : IDbContextFactory<Context>
{
public Context Create()
{
var s = (string)AppDomain.CurrentDomain.GetData("ConnectionStringName");
var context = new Context(s);
return context;
}
}
4) Mi configuración de migración ...
public sealed class Configuration : DbMigrationsConfiguration<SBD.Syrius.DataLayer.Context>
{
etc
}
5) Configure una función para crear el contexto.
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;
}
Mire este enlace: le da más libertad para activar las migraciones usted mismo para cada base de datos.
Lo resolví usando una cadena de conexión estática a una base de datos específica, dentro del constructor predeterminado.
Digamos que tengo varias bases de datos, todas están basadas en el mismo esquema: myCatalog1, myCatalog2, etc. Utilizo solo la primera cadena de conexión de base de datos en el constructor como esta:
public MyContext() : base("Data Source=./SQLEXPRESS;Initial Catalog=myCatalog1;Integrated Security=True")
{
// Can leave the rest of the constructor function itself empty
}
Este constructor se usa solo para que el comando Add-Migration
funcione y cree las migraciones. Tenga en cuenta que no hay efectos secundarios para el resto de las bases de datos y si necesita otro constructor para inicializar el contexto (para otros fines, excepto para las migraciones), funcionará.
Después de ejecutar el Add-Migration
esta manera:
Add-Migration -ConfigurationTypeName YourAppName.YourNamespace.Configuration "MigrationName"
Puedo llamar al siguiente código ( tomado del enlace proporcionado al principio ) para actualizar las migraciones a cada una de mis bases de datos que se basan en el mismo esquema 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();
}
Para las migraciones, puede (1) usar MigrateDatabaseToLatestVersion
que se activará automáticamente cuando comience a usar cualquiera de las entidades en su contexto o (2) usar DbMigrator
para decirle explícitamente a EF que DbMigrator
la migración. La ventaja de (2) es que no tiene que realizar una operación ficticia (como AddJunk
en el ejemplo de @ philsoady), e incluso podría usar MigratorScriptingDecorator
si desea extraer el SQL de migración (vea el Ejemplo 2 en el código)
El truco con (2) parece estar en asegurar que la misma cadena de conexión sea utilizada consistentemente por las clases DbMigrationsConfiguration
y DbContext
. Tenga en cuenta que se DbMigration.Update
instancias de contextos múltiples durante el curso de DbMigration.Update
, todos los cuales llaman al constructor predeterminado del contexto (así que tenga cuidado si tiene más de un constructor). También tiene 2 opciones aquí: puede usar un connection string name
en la aplicación.config (pero luego no puede definir la cadena de conexión mediante programación) o compilar / hardcode / load etc ... una connection string
completa. Vea los comentarios en el código a continuación.
Probado en EF 6.0.1 y 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");
}
}
}
Quería migrar automáticamente cuando se ejecutaba en DEPURACIÓN para facilitar la tarea de los desarrolladores (el instalador de producción realiza las migraciones normalmente) pero tenía el mismo problema, una cadena de conexión especificada por el código se ignoraba al migrar.
Mi enfoque fue derivar los contextos de migración de este genérico que maneja "guardar" la cadena de conexión:
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)
{
}
}
La advertencia de ReSharper se debe a que los campos estáticos en una clase genérica son solo estáticos por tipo concreto, que en nuestro caso es exactamente lo que queremos.
Los contextos se definen como:
public class MyContext : MigrateInitializeContext<MyContext, Migrations.Configuration>
{
public MyContext()
{
}
public MyContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
public virtual DbSet<MyType> MyTypes { get; set; }
}
que se puede usar normalmente
Si su migración no funciona correctamente, intente configurar Database.Initialize(true)
en DbContext ctor.
public CustomContext(DbConnection connection)
: base(connection, true)
{
Database.Initialize(true);
}
Tengo un problema similar con las migraciones. Y en mi solución siempre tengo que configurar el inicializador de base de datos en ctor, como a continuación
public CustomContext(DbConnection connection)
: base(connection, true)
{
Database.SetInitializer(new CustomInitializer());
Database.Initialize(true);
}
En el inicializador personalizado debe implementar el InitalizeDatabase(CustomContex context)
, por ej.
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);
}
}
}
}
}
ACTUALIZADO