9

Rozpoczynam nowy projekt, który używa Entity Framework. Przeanalizowałem moje opcje, jak utworzyć bazę danych i odkryłem, że Migracje Code-First mają największy sens (patrz na dole, jeśli chcesz wiedzieć dlaczego). Code-First Migrations pozwala mi przejść do arbitralnego SQL, co oznacza, że ​​nadal mam pełną kontrolę. W praktyce okazało się, że problem polegający na tym, że SQL jest strasznie powtarzalny w przypadku niektórych typowych zadań.Czy istnieje dobry sposób na rozszerzenie Code-First Migrations

Dla moich celów, nie obchodzi mnie, że rozszerzenia w migracji są niezależne od dostawcy (SQL, który wprowadzam, nie jest). Jednak tak naprawdę nie znajduję dobrego punktu łączenia ani rozszerzenia w strukturze migracji dla dodawania takich rzeczy.

Aby podać konkretny przykład, załóżmy, że chcę podać kolumnę RowGuid dla replikacji MS-SQL. Każdy occurance przybiera formę

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

Więc piszę statyczne metody, aby pozbyć się niektórych z redundancją

Sql(MigrationHelper.SetRowGuid(table, column); 

-lub-

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

Ewentualnie mógłby zrobić jeden z tych rozszerzenia metody na DbMigration i dostęp do nich przez this. Ale nadal wygląda to na nie na miejscu:

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", "''"): 

To nie jest straszne, ale wciąż wydaje mi się, że jest dla mnie hackerem. Muszę powtórzyć nazwę tabeli i muszę się upewnić, że otrzymałem poprawnie wygenerowaną nazwę kolumny. Uważam, że nazwa tabeli musi być wielokrotnie powtarzana, ale także kolumny. Ale tak naprawdę staram się zrobić jako dodatek do deklaracji tabeli, która właśnie się wydarzyła, gdy wszystkie nazwy tabel i nazwy kolumn były znane.

Ja jednak nie mogłem znaleźć dobrego punktu rozszerzenia dla rozszerzenia płynnego interfejsu lub w inny sposób rozszerzenia pierwszych migracji kodu w sposób, który wydaje się spójny. Czy czegoś brakuje? Czy ktoś ma dobry sposób na zrobienie tego?

Niektóre uzasadnienie, dlaczego jestem w tej sytuacji:

I nie podoba mi się to, co wydawało się wspólnego rozwiązania z użyciem wspólnego atrybuty niestandardowe rozwiązanie, aby wskazać bazę danych non-mapowania z kilku powodów, ale najsilniej ponieważ nie są one automatycznie zbierane przez migracje, co oznacza dodatkową konserwację. Pierwsze rozwiązania były niedostępne, ponieważ nie zapewniają pełnej kontroli nad bazą danych. Baza-Pierwsza była atrakcyjna ze względu na kontrolę; jednak nie ma gotowej funkcji zarządzania zmianą, którą zapewnia Code-First Migration. Tak więc pierwsze migracje Code-First wydawały się być zwycięzcami, ponieważ zmiany oparte na modelach [code-first] są automatyczne i oznaczały, że utrzymanie tylko jednej rzeczy.

+0

+1 dla podkreślając EFCF jako jedynego podejścia Entity Framework, która naprawdę ułatwia kontrolę zmian. – bwerks

Odpowiedz

5

Znalazłem rozwiązanie, chociaż nie jestem pewien, czy jest dobre. Musiałem pójść nieco dalej w króliczej dziurze, niż chciałem ją zdobyć, i to nie jest tak naprawdę punkt rozszerzenia.

To pozwala mi pisać sprawozdania, takie jak:

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"); 

nie lubię:

  • Fakt, że jestem odzwierciedlając na członków prywatnych, i normalnie nie stosować takiego rozwiązania
  • Aby lambda mogła wybrać kolumnę, można zwrócić niewłaściwą nazwę kolumny, jeśli użyto parametru opcjonalnego "nazwa" definicji kolumny.

jestem tylko rozważa wykorzystanie go tutaj, ponieważ:

  • Wysyłamy zespół EF więc jesteśmy pewni, jeden używany będzie miał tych członków.
  • Kilka testów jednostkowych powie nam, czy nowa wersja je złamie.
  • Jest izolowany od migracji.
  • Mamy wszystkie informacje, które analizujemy, więc jeśli nowa wersja to przełamie, możemy wprowadzić hack, aby zastąpić tę funkcję.
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 za używanie '[Pure]' – nicodemus13

0

Nie jest to ogólne rozwiązanie, ale można je dziedziczyć po klasie abstrakcji/interfejsu. Biorąc to pod uwagę, potrzebowałoby to pewnych zmian w kodzie, ale w miarę rozsądne.

Użyłem tego wzorca do zdefiniowania kolumn audytu (Zaktualizowany, AktualizacjaData itd.) Dla wszystkich tabel.

Powiązane problemy