9

Estoy comenzando un nuevo proyecto que usa Entity Framework. Investigué mis opciones sobre cómo crear la base de datos y encontré que Code-First Migrations tiene más sentido (ver abajo si necesita saber por qué). Code-First Migrations me permite pasar a SQL arbitrario, lo que significa que aún tengo control total. En la práctica, encontré que el problema es que bajar a SQL parece terriblemente repetitivo para algunas de las tareas comunes.¿Existe alguna manera de extender las primeras migraciones de código?

Para mis propósitos, no me importa que las extensiones en la migración sean independientes del proveedor (el SQL que estoy insertando no lo está). Sin embargo, realmente no encuentro una buena costura o punto de extensión en el marco de migraciones para agregar tales cosas.

Para dar un ejemplo concreto, supongamos que quiero especificar una columna RowGuid para la replicación de MS-SQL. Cada ocurrencia toma la forma de

Sql(
    string.Format(
     "Alter Table {0} Alter Column {1} Add ROWGUIDCOL", 
     table, 
     column)); 

así escribo métodos estáticos para deshacerse de parte de la redundancia

Sql(MigrationHelper.SetRowGuid(table, column); 

-o-

MigrationHelper.SetRowGuid(Sql, table, column); //passing the Sql method 

podría hacer que cualquiera de los de extensión métodos en DbMigration, y acceda a ellos por this. Pero aún esto parece fuera de lugar:

CreateTable(
    "dbo.CustomerDirectory", 
    c => new 
     { 
      Uid = c.Int(nullable: false), 
      CustomerUid = c.Int(nullable: false), 
      Description = c.String(nullable: false, maxLength: 50, unicode: false), 
      RowGuid = c.Guid(nullable: false), 
     }) 
    .PrimaryKey(t => t.Uid) 
    .ForeignKey("dbo.Customer", t => t.CustomerUid); 

this.SetRowGuid(Sql, "dbo.CustomerDirectory", "RowGuid"); 
//Custom method here because of desired naming convention of Constraint 
this.SetDefaultConstraint(Sql, "dbo.CustomerDirectory", "''"): 

No es terriblemente, pero todavía se siente como un truco para mí. Tengo que repetir el nombre de la tabla, y necesito asegurarme de que el nombre de la columna generada sea correcto. Creo que el nombre de la tabla debe repetirse mucho, pero también lo hacen las columnas. Sin embargo, lo que realmente intento hacer es agregar a la tabla una declaración que acaba de ocurrir en la que se conocen todos los nombres de las tablas y los nombres de las columnas.

Yo, sin embargo, no pude encontrar un buen punto de extensión para extender la interfaz con fluidez o extender las primeras migraciones del código de una manera que se siente consistente. ¿Me estoy perdiendo de algo? ¿Alguien ha encontrado una buena manera de hacer esto?

Algunos justificación de por qué estoy en esta situación:

que no le gustaba el lo que parecía ser una solución común de utilizar costumbre común solución de atributos para indicar la base de datos no mapeo por varias razones, pero más fuertemente porque no son recogidos automáticamente por migraciones, lo que significa un mantenimiento adicional. Las soluciones de primer modelo estaban fuera porque no otorgan un control total sobre la base de datos. Database-First fue atractivo debido al control; sin embargo, no tiene la funcionalidad de administración de cambios lista para usar que proporciona Code-First Migrations. Por lo tanto, las migraciones de código primero parecían ser un ganador porque los cambios impulsados ​​por el modelo [primer código] son ​​automáticos y significaba que habría una sola cosa que mantener.

+0

+1 para destacar EFCF como el único enfoque Marco de la entidad que realmente facilita el control de cambios. – bwerks

Respuesta

5

He encontrado una solución, aunque no estoy seguro si es buena. Tuve que ir un poco más allá del agujero del conejo de lo que quería conseguirlo, y no es realmente un punto de extensión.

Se me permite escribir frases como:

CreateTable(
    "dbo.CustomerDirectory", 
    c => new 
     { 
      Uid = c.Int(nullable: false), 
      CustomerUid = c.Int(nullable: false), 
      Description = c.String(nullable: false, maxLength: 50, unicode: false), 
      RowGuid = c.Guid(nullable: false), 
     }) 
    .PrimaryKey(t => t.Uid) 
    .ForeignKey("dbo.Customer", t => t.CustomerUid) 
     //SqlValue is a custom static helper class 
    .DefaultConstraint(t => t.Description, SqlValue.EmptyString) 
     //This is a convention in the project 
     //Equivalent to 
     // .DefaultConstraint(t => t.RowGuid, SqlValue.EmptyString) 
     // .RowGuid(t => t.RowGuid) 
    .StandardRowGuid() 
     //For one-offs 
    .Sql(tableName => string.Format("ALTER TABLE {0} ...", tableName"); 

que no me gustan:

  • El hecho de que yo estoy reflexionando sobre los miembros privados, y normalmente no deberían utilizar una solución de este tipo
  • Que la lambda para seleccionar una columna podría devolver el nombre de columna incorrecto si se utilizó el parámetro opcional "nombre" de la definición de columna.

sólo estoy pensando en utilizar aquí porque:

  • Enviamos el montaje EF así que estamos seguros la utilizada tendrán estos miembros.
  • Un par de pruebas nos dirá si una nueva versión las romperá.
  • Está aislado para migraciones.
  • Tenemos toda la información que estamos tratando de obtener, así que si una nueva versión rompe esto, podríamos poner en marcha un truco para reemplazar esta funcionalidad.
internal static class TableBuilderExtentions 
{ 
    internal static TableBuilder<TColumns> Sql<TColumns>(
     this TableBuilder<TColumns> tableBuilder, 
     Func<string, string> sql, 
     bool suppressTransaction = false, 
     object anonymousArguments = null) 
    { 
     string sqlStatement = sql(tableBuilder.GetTableName()); 

     DbMigration dbMigration = tableBuilder.GetDbMigration(); 
     Action<string, bool, object> executeSql = dbMigration.GetSqlMethod(); 

     executeSql(sqlStatement, suppressTransaction, anonymousArguments); 

     return tableBuilder; 
    } 

    [Pure] 
    private static DbMigration GetDbMigration<TColumns>(this TableBuilder<TColumns> tableBuilder) 
    { 
     var field = tableBuilder.GetType().GetField(
      "_migration", BindingFlags.NonPublic | BindingFlags.Instance); 
     return (DbMigration)field.GetValue(tableBuilder); 
    } 

    /// <summary> 
    /// Caution: This implementation only works on single properties. 
    /// Also, coder may have specified the 'name' parameter which would make this invalid. 
    /// </summary> 
    private static string GetPropertyName<TColumns>(Expression<Func<TColumns, object>> someObject) 
    { 
     MemberExpression e = (MemberExpression)someObject.Body; 

     return e.Member.Name; 
    } 

    [Pure] 
    private static Action<string, bool, object> GetSqlMethod(this DbMigration migration) 
    { 
     MethodInfo methodInfo = typeof(DbMigration).GetMethod(
      "Sql", BindingFlags.NonPublic | BindingFlags.Instance); 
     return (s, b, arg3) => methodInfo.Invoke(migration, new[] { s, b, arg3 }); 
    } 

    [Pure] 
    private static string GetTableName<TColumns>(this TableBuilder<TColumns> tableBuilder) 
    { 
     var field = tableBuilder.GetType().GetField(
      "_createTableOperation", BindingFlags.NonPublic | BindingFlags.Instance); 

     var createTableOperation = (CreateTableOperation)field.GetValue(tableBuilder); 
     return createTableOperation.Name; 
    } 
} 
+2

+1 para usar '[Pure]' – nicodemus13

0

No es una solución genérica, pero puede heredar de una clase abstracta/de interfaz. Dado esto, necesitaría algunos cambios de código, pero es razonablemente limpio.

He utilizado este patrón para definir mis columnas de auditoría (UpdatedBy, UpdateDate etc.) para todas las tablas.

Cuestiones relacionadas