Pagine

mercoledì 2 dicembre 2015

E.F. Code First - le migrations

Quando si utilizza in E.F. il code - first in pochi sanno che si può eseguire del codice sql arbitrario durante la migration.



Supponiamo ad esempio d'avere il seguente schema di partenza :
E di voler passare al seguente schema di destinazione :
Durante il passaggio dal primo schema al secondo schema, si vuole preservare i dati e quindi eseguire un update durante la migration.

Come fare? Si crea prima la migration utilizzando il comando dal package-manager add-migration

Genererà un codice simile a questo :

public partial class SistemazioneVolo : DbMigration
{
  public override void Up()
  {
    DropForeignKey("dbo.Voli", "IdConnessione", "dbo.Connessiones");
    DropIndex("dbo.Voli", new[] { "IdConnessione" });
    AddColumn("dbo.Voli", "IdCittaFrom", c => c.Int());
    AddColumn("dbo.Voli", "IdCittaTo", c => c.Int());
    CreateIndex("dbo.Voli", "IdCittaPartenza");
    CreateIndex("dbo.Voli", "IdCittaDestinazione");
    AddForeignKey("dbo.Voli", "IdCittaFrom", "dbo.Citta", "ID");
    AddForeignKey("dbo.Voli", "IdCittaTo", "dbo.Citta", "ID");
    DropColumn("dbo.Voli", "IdConnessione");
  }

  public override void Down()
  {
    AddColumn("dbo.Voli", "IdConnessione", c => c.Int());
    CreateIndex("dbo.Voli", "IdConnessione");
    AddForeignKey("dbo.Voli", "IdConnessione", "dbo.Connessiones", "Id");
    DropForeignKey("dbo.Voli", "IdCittaFrom", "dbo.Citta");
    DropForeignKey("dbo.Voli", "IdCittaTo", "dbo.Citta");
    DropIndex("dbo.Voli", new[] { "IdCittaFrom" });
    DropIndex("dbo.Voli", new[] { "IdCittaTo" });
    DropColumn("dbo.Voli", "IdCittaDestinazione");
    DropColumn("dbo.Voli", "IdCittaPartenza");
  }
}
Dovremmo quindi nel metodo Up agguingere lo script di migrazione SQL :
update Voli set [IdCittaFrom] = c.IdCittaFrom,  [IdCittaTo] =c.IdCittaTo from Voli v inner join Connessiones c on v.IdConnessione = c.Id

Il metodo Up diventerà quindi :
 public override void Up()
  {
    DropForeignKey("dbo.Voli", "IdConnessione", "dbo.Connessiones");
    DropIndex("dbo.Voli", new[] { "IdConnessione" });
    AddColumn("dbo.Voli", "IdCittaFrom", c => c.Int());
    AddColumn("dbo.Voli", "IdCittaTo", c => c.Int());
    CreateIndex("dbo.Voli", "IdCittaPartenza");
    CreateIndex("dbo.Voli", "IdCittaDestinazione");
    AddForeignKey("dbo.Voli", "IdCittaFrom", "dbo.Citta", "ID");
    AddForeignKey("dbo.Voli", "IdCittaTo", "dbo.Citta", "ID");
    Sql("update Voli set [IdCittaFrom] = c.IdCittaFrom,  [IdCittaTo] =c.IdCittaTo from Voli v inner join Connessiones c on v.IdConnessione = c.Id");
    DropColumn("dbo.Voli", "IdConnessione");
  }
Se vogliamo dare la compatibilità anche nel "tornare indietro" alla migrazione precedente dovremo provedere alla creazione dello script sql nel metodo Down.
Oltre a degli update possiamo fare degli insert, ad esempio nel caso di creazione di dizionari che contengano dei valori predefiniti.
Se si hanno però molte insert da fare, forse è il caso di spostare questi ultimi in uno script sql da eseguire durante la migrazione.
Ad esempio :
public partial class AggiuntoCapAiComuni : ExpandedDbMigration
{
  public override void Up()
  {
    ...
    SqlFile(@"Bin\Migrations\Sql\InserimentoComuni.sql");
  }
        
  public override void Down()
  {
    ...
  }
}
Come potrete notare, in questo caso abbiamo cambiato la classe base, non più DbMigration bensì ExpandedDbMigration. Questo il codice :
public abstract class ExpandedDbMigration : System.Data.Entity.Migrations.DbMigration
{
 /// 
 /// Drops an index of the name specified, on the table specified.
 /// 
 /// The built-in DropIndex command in Migrations interprets your command in a variety of ways that can translate into
 /// multiple SQL statements, or dropping an index with an expanded name based on Conventions. This command cuts past those
 /// interpretations and drops exactly what you requested.
 /// 
 /// Useful on existing databases where indices pre-existed, or where indices have been created by an outside tool like
 /// Sql Server Missing Indexes.
 /// 
 public void DropIndexExact(string table, string indexName)
 {
  Sql("drop index [" + indexName + "] on [" + table + "]");
 }

 public void DropConstraint(string table, string constraintName)
 {
  Sql("alter table [" + table + "] drop constraint [" + constraintName + "]");
 }

 /// 
 /// Drop a Primary Key by name. See DropIndexExact for an explanation of how Exact calls differ from EF Migrations built-ins.
 /// 
 public void DropPrimaryKeyExact(string table, string pkName)
 {
  DropConstraint(table, pkName);
 }

 /// 
 /// Drop a Foreign Key by name. See DropIndexExact for an explanation of how Exact calls differ from EF Migrations built-ins.
 /// 
 public void DropForeignKeyExact(string table, string fkName)
 {
  DropConstraint(table, fkName);
 }

 /// 
 /// Reads in a .sql file filled with SQL statements and emits them as part of a Migration's data changes ("data motion").
 /// 
 /// Usage: SqlFile(@"Migrations\Sql\2013-08-15 FillTable.sql");
 /// 
 /// Note: All statements will presumably operate on specific table names, which will need to exist in this database
 /// already to work properly.
 /// 
 /// Note: Any error will cause the entire Migration to fail, as EF Migrations always do.
 /// 
 /// Note: Many statements common in SQL scripts are illegal in Migrations commands, especially "GO".
 /// To compensate for this, this call tries to filter out common problem statements - but it might be wise to clean out
 /// your sql script beforehand. Or, just run it and see what happens - the migration always runs in a Transaction so if it
 /// fails no harm done.
 /// 
 /// The path to the .sql script relative to the Project that contains this Migration.
 public void SqlFile(string path)
 {
  var cleanAppDir = new Regex(@"\\bin.+");
  var dir = AppDomain.CurrentDomain.BaseDirectory;
  dir = cleanAppDir.Replace(dir, "") + @"\";
  var sql = File.ReadAllLines(dir + path);

  string[] ignore = new string[]
  {
   "GO", // Migrations doesn't support GO
   "/*", // Migrations might not support comments
   "print" // Migrations might not support print
  };

  foreach (var line in sql)
  {
   if (ignore.Any(ig => line.StartsWith(ig)))
    continue;
   if (!string.IsNullOrEmpty(line.Trim()))
   {
    Sql(line);    
   }
   
  }
 }
}