Люблю я велосипеды.
В процессе написания ПО с использование EF, часто меняется структура, что создает проблемы. До версии 4.3 — пересоздание БД с потерей данных и необходимостью наполнения тестовыми.
С версии 4.3. появилась Миграция. Миграция сняла головную боль с удалением всех данных. Но ручное описание механизма обновления БД — мне надоело после 2 миграции. Автоматическая миграция — то что нужно. Минус один, при сильном изменении структуры (тип поля, поле из 1 таблицы в другую) — теряются данные.
В итоге появился велосипед.
При изменении структуры БД.
1. Определяет версию с прошлого обновления;
2. Отсоединяет БД от SQL сервера;
3. Копирует файлы БД (рядом с оригиналом) ФАЙЛБД -> ФАЙЛБД_ВЕРСИЯ;
4. Присоединяет оригинал под старым именем;
5. Присоединяет копию под именем СТАРОЕ.ВЕРСИЯ;
6. Применяет автоматическую миграцию;
7. Записываю версию;
Главный плюсы для меня:
1. Обновление структуры без лишних вопросов;
2. Сохранность накопленных данных, которые можно перетянуть уже вручную (T-SQL мы не боимся :))
public class RenameCreateDatabaseIfModelChanged<TContext> : IDatabaseInitializer<TContext> where TContext : System.Data.Entity.DbContext
{
public void InitializeDatabase(TContext context)
{
int version = 1;
DbCommand cmd;
if (context.Database.Exists())
{
bool throwIfNoMetadata = true;
if (context.Database.CompatibleWithModel(throwIfNoMetadata))
{
return;
}
DbDataReader dr;
context.Database.Connection.Open();
//GET VERSION
cmd = context.Database.Connection.CreateCommand();
cmd.CommandText = "SELECT TOP 1 * FROM sysobjects WHERE xtype='U' AND name = '__ase.version'";
dr = cmd.ExecuteReader();
if (dr.Read())
{
//VERSION EXISTS
dr.Close();
cmd.CommandText = "SELECT TOP 1 Vesion FROM [__ase.version] ORDER BY CreatedOn DESC";
dr = cmd.ExecuteReader();
if (dr.Read())
version = (int)dr["Vesion"];
dr.Close();
version++;
}
else
{
//First
dr.Close();
cmd.CommandText = "CREATE TABLE [__ase.version] ([Vesion] [int] NOT NULL, [CreatedOn] [datetime] NOT NULL)";
cmd.ExecuteNonQuery();
//WriteVersion(context, version);
}
//Get list files
List<string> files = new List<string>();
cmd.CommandText = "EXEC SP_HELPFILE";
dr = cmd.ExecuteReader();
while (dr.Read())
{
files.Add(dr["filename"].ToString());
}
dr.Close();
//Disconnect all connections
cmd.CommandText = String.Format("ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE", context.Database.Connection.Database);
cmd.ExecuteNonQuery();
cmd.CommandText = String.Format("ALTER DATABASE [{0}] SET MULTI_USER WITH ROLLBACK IMMEDIATE", context.Database.Connection.Database);
cmd.ExecuteNonQuery();
string dbName = context.Database.Connection.Database;
//Deattach database
cmd.CommandText = String.Format("USE MASTER; EXEC SP_DETACH_DB [{0}]", dbName);
cmd.ExecuteNonQuery();
//Copy database
string sql_file_old = String.Format("EXEC SP_ATTACH_DB [{0}], ", dbName);
string sql_file_new = String.Format("EXEC SP_ATTACH_DB [{0}.{1}], ", dbName, version);
foreach (string file in files)
{
File.Copy(file, file + "_" + version);
sql_file_old += "'" + file + "', ";
sql_file_new += "'" + file + "_" + version + "', ";
}
//Attach database
cmd.CommandText = sql_file_old.Substring(0, sql_file_old.Length - 2);
cmd.ExecuteNonQuery();
//Attach copy database
cmd.CommandText = sql_file_new.Substring(0, sql_file_new.Length - 2);
cmd.ExecuteNonQuery();
context.Database.Connection.Close();
}
//Migrate with data loss
var configuration = new DbMigrationsConfiguration<TContext>();
configuration.AutomaticMigrationDataLossAllowed = true;
configuration.AutomaticMigrationsEnabled = true;
var migrator = new DbMigrator(configuration);
migrator.Update();
//Update version
context.Database.Connection.Open();
cmd = context.Database.Connection.CreateCommand();
DbParameter param = cmd.CreateParameter();
param.ParameterName = "<hh user=v1>";
param.Value = version;
cmd.Parameters.Add(param);
param = cmd.CreateParameter();
param.ParameterName = "<hh user=v2>";
param.Value = DateTime.Now;
cmd.Parameters.Add(param);
cmd.CommandText = "INSERT INTO [__ase.version] ([Vesion], [CreatedOn]) VALUES (<hh user=v1>, <hh user=v2>)";
cmd.ExecuteNonQuery();
context.Database.Connection.Close();
}
protected virtual void Seed(TContext context)
{
}
}
public class InitData : RenameCreateDatabaseIfModelChanged<AppEntities>
{
protected override void Seed(AppEntities context)
{
base.Seed(context);
}
}
System.Data.Entity.Database.SetInitializer<AppEntities>(new InitData());
Автор: AlexandrDP