¿Cuál es el uso correcto de IDatabaseInitializer en EF?

c# code-first-migrations ef-code-first entity-framework entity-framework-6

Pregunta

Tengo un DatabaseInitialiser personalizado que está abajo

/// <summary>
/// Implements the IDatabaseInitializer to provide a custom database initialisation for the context.
/// </summary>
/// <typeparam name="TContext">TContext is the DbContext</typeparam>
public class ParikshaDataBaseInitializer<TContext> : IDatabaseInitializer<TContext> where TContext : DbContext
{
    /// <summary>
    /// The method to Initialise the database.
    /// Takes care of the database cannot be dropped since it is in use problem while dropping and recreating the database.
    /// </summary>
    /// <param name="context">The DbContext on which to run the initialiser</param>
    public void InitializeDatabase(TContext context)
    {
        var exists = context.Database.Exists();

        try
        {
            if (exists && context.Database.CompatibleWithModel(true))
            {
                // everything is good , we are done
                return;
            }

            if (!exists)
            {
                context.Database.Create();
            }
        }
        catch (Exception)
        {
            //Something is wrong , either we could not locate the metadata or the model is not compatible.
            if (exists)
            {
                context.Database.ExecuteSqlCommand("ALTER DATABASE Pariksha SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
                context.Database.ExecuteSqlCommand("USE Master DROP DATABASE Pariksha");
                context.SaveChanges();
            }

            context.Database.Create();
        }
    } 
}

Algo sobre el código anterior es más que solo hacky (siéntase libre de participar con ayuda)

Luego agregué migraciones y conseguí que el script de migración también funcionara correctamente.

/// <summary>
/// Implements the IDatabaseInitializer to provide a custom database initialisation for the context.
/// </summary>
/// <typeparam name="TContext">TContext is the DbContext</typeparam>
public class ParikshaDataBaseInitializer<TContext> : IDatabaseInitializer<TContext> where TContext : DbContext
{
    /// <summary>
    /// The method to Initialise the database.
    /// Takes care of the database cannot be dropped since it is in use problem while dropping and recreating the database.
    /// </summary>
    /// <param name="context">The DbContext on which to run the initialiser</param>
    public void InitializeDatabase(TContext context)
    {
        var exists = context.Database.Exists();

        try
        {
            if (exists && context.Database.CompatibleWithModel(true))
            {
                // everything is good , we are done
                return;
            }

            if (!exists)
            {
                context.Database.Create();
            }
        }
        catch (Exception)
        {
            //Something is wrong , either we could not locate the metadata or the model is not compatible.
            if (exists)
            {
                context.Database.ExecuteSqlCommand("ALTER DATABASE Pariksha SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
                context.Database.ExecuteSqlCommand("USE Master DROP DATABASE Pariksha");
                context.SaveChanges();
            }

            context.Database.Create();
        }
    } 
}

Las migraciones funcionan como se espera.

Ahora, las preguntas están en el inicio de mi aplicación, ¿qué debo hacer? Algo como esto

/// <summary>
/// Implements the IDatabaseInitializer to provide a custom database initialisation for the context.
/// </summary>
/// <typeparam name="TContext">TContext is the DbContext</typeparam>
public class ParikshaDataBaseInitializer<TContext> : IDatabaseInitializer<TContext> where TContext : DbContext
{
    /// <summary>
    /// The method to Initialise the database.
    /// Takes care of the database cannot be dropped since it is in use problem while dropping and recreating the database.
    /// </summary>
    /// <param name="context">The DbContext on which to run the initialiser</param>
    public void InitializeDatabase(TContext context)
    {
        var exists = context.Database.Exists();

        try
        {
            if (exists && context.Database.CompatibleWithModel(true))
            {
                // everything is good , we are done
                return;
            }

            if (!exists)
            {
                context.Database.Create();
            }
        }
        catch (Exception)
        {
            //Something is wrong , either we could not locate the metadata or the model is not compatible.
            if (exists)
            {
                context.Database.ExecuteSqlCommand("ALTER DATABASE Pariksha SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
                context.Database.ExecuteSqlCommand("USE Master DROP DATABASE Pariksha");
                context.SaveChanges();
            }

            context.Database.Create();
        }
    } 
}

y algunos foros sugirieron esto también en el constructor, que parece un poco extraño porque no quiero comprobar si la db y el esquema son correctos cada vez que uso el Contexto. Entonces, ¿cuál podría ser el posible uso de esta técnica o obtuve el contexto de la sugerencia como incorrecta?

/// <summary>
/// Implements the IDatabaseInitializer to provide a custom database initialisation for the context.
/// </summary>
/// <typeparam name="TContext">TContext is the DbContext</typeparam>
public class ParikshaDataBaseInitializer<TContext> : IDatabaseInitializer<TContext> where TContext : DbContext
{
    /// <summary>
    /// The method to Initialise the database.
    /// Takes care of the database cannot be dropped since it is in use problem while dropping and recreating the database.
    /// </summary>
    /// <param name="context">The DbContext on which to run the initialiser</param>
    public void InitializeDatabase(TContext context)
    {
        var exists = context.Database.Exists();

        try
        {
            if (exists && context.Database.CompatibleWithModel(true))
            {
                // everything is good , we are done
                return;
            }

            if (!exists)
            {
                context.Database.Create();
            }
        }
        catch (Exception)
        {
            //Something is wrong , either we could not locate the metadata or the model is not compatible.
            if (exists)
            {
                context.Database.ExecuteSqlCommand("ALTER DATABASE Pariksha SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
                context.Database.ExecuteSqlCommand("USE Master DROP DATABASE Pariksha");
                context.SaveChanges();
            }

            context.Database.Create();
        }
    } 
}

Resumir,

  1. ¿Cuál es el caso de uso correcto para las diferentes técnicas disponibles?

  2. ¿Cuál sería la estrategia ideal para que las migraciones funcionen en todas las condiciones y cuando movamos las bases de datos de un entorno a otro?

Respuesta aceptada

Este es mi intento en Db Initializer que combina tanto el inicializador de Migration como el Db Create uno predeterminado con la inicialización. (nota: no es lo ideal, es más como un ejercicio simple, pero brinda una solución a lo que está preguntando aquí, en su mayoría funciona, solo revise todas las actualizaciones que hice).

¿Cómo crear un inicializador para crear y migrar la base de datos mysql?

En cuanto al why y how : para entender completamente, le sugiero que también consulte el código fuente de EF (que es una versión nueva pero en muchos aspectos es similar)

1)

a) El inicializador Db se llama generalmente solo una vez (por conexión), y cuando intenta acceder a su 'modelo' por primera vez (primera consulta o similar). Ponga un punto de interrupción en su inicializador para comprobar.

Así que es completamente seguro ponerlo dentro del constructor (aunque lo prefiero en el inicio, config también). Solo se llama cuando se necesita para inicializar (y se usa el last one set ), no se debe invocar manualmente.

De todos modos, para forzar el inicializador puede hacer this.Database.Initialize(force: true);

Para cuando cambiar conexiones veas mi post sobre problemas.
Codifique la primera cadena de conexión personalizada y las migraciones sin usar IDbContextFactory

b) Si crea su propio IDatabaseInitializer y aún desea que las migraciones trabajen side by side

No debería simplemente llamar a DbMigrator desde el exterior , ya que su inicializador personalizado se perderá toda la 'creación de db' (p. Ej., Si desea propagar o algo, consulte mi ejemplo anterior).

Ambas cosas son efectivamente "inicializadores", por lo que necesitarías integrarlos en uno , eso chain cosas de alguna manera. Tenga en cuenta que el order of execution es importante (consulte el ejemplo anterior para ver los problemas): debe verificar la "condición de vacío", luego llamar a DbMigrator y luego realizar su propia inicialización. Utilicé un inicializador como clase base y fusioné el otro.

Y si solo desea seed , puede usar la Configuración de migración, eso es lo más simple si es posible.

2)

Es bastante 'abierto' y no hay una respuesta única. Por lo general, funciona, pero los problemas están expexted ...

  • Las migraciones son 3 cosas (como lo veo): su modelo de código / entidades, su base de datos / tablas y la tabla del sistema __MigrationHistory en la Db. Los 3 necesitan estar in sync . Si no está sincronizado, puede descartar la tabla de migración, volver a crear migraciones (con una bandera para mantener la base de datos existente) y luego continuar como antes, es decir, hay soluciones para cuando con datos en vivo. Para esto vea Cómo ignorar una tabla / clase en las migraciones de EF 4.3 ,

  • necesitaría permisos para soltar / crear Db para cuando mueva la base de datos,

  • asegúrese de que su conexión es correcta (cambio en la configuración y en sincronización con su nombre o ctor DbContext),

  • manténgalo simple, no haga cosas extravagantes ni cambie conexiones de código (es posible pero tiene problemas), etc.

  • no mix database / code versiones de mix database / code - es decir, una versión de entidades de código - una base de datos. Si desea compartir la misma Db con diferentes versiones de código (por ejemplo, puesta en escena, producción) - no (las soluciones para múltiples inquilinos estarán disponibles en EF6 - por ejemplo, esto ),

  • si necesita aplicar manualmente la base de datos (generar el script través Update-Database y aplicar eso, no haga las cosas de forma manual o se equivocará (la tabla del historial de migración), vea esta

... eso es solo para nombrar unos pocos. Es una OMI bastante estable y utilizable, pero si sigue las reglas y sabe cuáles son las limitaciones.


class CreateAndMigrateDatabaseInitializer<TContext, TConfiguration> 
    : CreateDatabaseIfNotExists<TContext>, IDatabaseInitializer<TContext>
    where TContext : DbContext
    where TConfiguration : DbMigrationsConfiguration<TContext>, new()
{
    private readonly DbMigrationsConfiguration _configuration;
    public CreateAndMigrateDatabaseInitializer()
    {
        _configuration = new TConfiguration();
    }
    public CreateAndMigrateDatabaseInitializer(string connection)
    {
        Contract.Requires(!string.IsNullOrEmpty(connection), "connection");

        _configuration = new TConfiguration
        {
            TargetDatabase = new DbConnectionInfo(connection)
        };
    }
    void IDatabaseInitializer<TContext>.InitializeDatabase(TContext context)
    {
        var doseed = !context.Database.Exists();
        // && new DatabaseTableChecker().AnyModelTableExists(context);
        // check to see if to seed - we 'lack' the 'AnyModelTableExists'
        // ...could be copied/done otherwise if needed...

        var migrator = new DbMigrator(_configuration);
        // if (doseed || !context.Database.CompatibleWithModel(false))
        if (migrator.GetPendingMigrations().Any())
            migrator.Update();

        // move on with the 'CreateDatabaseIfNotExists' for the 'Seed'
        base.InitializeDatabase(context);
        if (doseed)
        {
            Seed(context);
            context.SaveChanges();
        }
    }
    protected override void Seed(TContext context)
    {
    }
}

Respuesta popular

Resumir,

1) what is the correct use-case for the different techniques available ?

2) what would be the ideal strategy so that the migrations work in all conditions 
and when we move databases from one environment to another ?

Retroceda y pregúntese cuándo desea que EF realice las inicializaciones y migraciones. En mi organización (empresa), tenemos reglas muy estrictas sobre cómo se migran el código, los datos y DDL, por lo que estas características no nos ofrecen ningún valor. Incluso si pudiéramos usarlos, sería muy cauteloso hasta que tuviera mucha experiencia con la herramienta en diferentes entornos.

Entonces, ¿por qué usarlo? Estás comenzando un nuevo proyecto de campo verde y estás usando el código primero. Ahora, aquí es donde es útil. Puede escribir su código, compilar, ejecutar y dejar que EF se preocupe por los campos, relaciones y tablas nuevos o faltantes. Más tarde, puede formalizarlo en su servidor de desarrollo, y luego eliminar los inicializadores y los migrantes para siempre.

En cuanto a cómo configurarlo? Parece que has encontrado una manera que funciona. Estoy de acuerdo en que parece un poco hacky, pero el código es similar a mi configuración inicial. Estoy usando MVC.NET, así que usé lo siguiente en Global.asax.cs:

1) what is the correct use-case for the different techniques available ?

2) what would be the ideal strategy so that the migrations work in all conditions 
and when we move databases from one environment to another ?

La conclusión es que creo que tu (2) es la pregunta incorrecta. Debería ser: "¿Realmente quiero hacerlo de esta manera?". Puede suceder que lo haga, pero consideraría otras opciones más tradicionales primero, especialmente si está en una compañía de más de 10 personas.




Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué