Pull to refresh

Мой велосипед Entity FrameWork, Auto Migration, With Save Data

Reading time 3 min
Views 3.4K
Люблю я велосипеды.

В процессе написания ПО с использование 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 = "@v1";
            param.Value = version;
            cmd.Parameters.Add(param);

            param = cmd.CreateParameter();
            param.ParameterName = "@v2";
            param.Value = DateTime.Now;
            cmd.Parameters.Add(param);

            cmd.CommandText = "INSERT INTO [__ase.version] ([Vesion], [CreatedOn]) VALUES (@v1, @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());
Tags:
Hubs:
+6
Comments 87
Comments Comments 87

Articles