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

    Люблю я велосипеды.

    В процессе написания ПО с использование 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());
    
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 87

      +1
      Отсоединяет БД от SQL сервера; Копирует файлы БД (рядом с оригиналом) ФАЙЛБД -> ФАЙЛБД_ВЕРСИЯ; [...] Присоединяет оригинал под старым именем;

      Простите, сколько у вас даунтайм при базе размером гигов в триста-четыреста?

      Миграция сняла головную боль с удалением всех данных. Но ручное описание механизма обновления БД — мне надоело после 2 миграции. Автоматическая миграция — то что нужно. Минус один, при сильном изменении структуры (тип поля, поле из 1 таблицы в другую) — теряются данные.

      А что мешает сначала сделать scaffold миграции, чтобы записалось изменение структуры, а потом дописать к нему перенос данных?
        0
        Основная задача велосипеда — облегчить разработку, не публикацию.
          0
          Для разработки достаточно DropCreateAlways.

          Миграции — это решение для переноса в продуктив в первую очередь.
            +1
            А разработчику не нужно?
              –1
              Не нужно что?
                +1
                Миграция. Обновление структуры с сохранением данных.
                  0
                  А зачем, если вкратце?

                  Зачем разработчику сохранять (заведомо тестовые) данные?
                    0
                    Возможно у них нет заранее подготовленных тестовых наборов данных, так что вроде данные и не нужны, но и терять их не хочется.
                      +1
                      Ну вообще-то это классический такой bad practice. Тестовые данные — они на то и тестовые, чтобы быть заранее хорошо известными.
                        +1
                        Классический с чьей точки зрения?
                          0
                          «классиков». Как я уже писал, я опираюсь на Continuous Delivery в первую очередь, но в принципе, в отношении well-defined test data это действительно универсальный подход, вплоть до Software Requirements Вигерса.
                            +1
                            К счастью, я не использую опыт классиков, которые не рекомендуют использовать накопленные данные.
                              0
                              Классики рекомендуют использовать накопленные данные по определенным правилам, а не просто так.
                                0
                                Теперь данные делятся на правильные и неправильные?
                                Так тестовые как-раз, могут быть оторванными от действительности.
                                  0
                                  Теперь данные делятся на правильные и неправильные?

                                  Ну вообще да, что вас удивляет?

                                  Так тестовые как-раз, могут быть оторванными от действительности.

                                  Это вопрос квалификации человека, который их готовил.
                                    0
                                    Если данные накоплены, и их нельзя терять — они правильные.
                                    Данные не зависят от квалификации, они или есть или нет.
                                    Агрегация зависит.
                                      0
                                      Почему их нельзя терять? В вашем конкретном случае, почему данные, которые использует разработчик на своей машине, нельзя терять?

                                      Данные не зависят от квалификации, они или есть или нет

                                      Это нонсенс. Тестовые данные готовит человек. Соответственно, они зависят от его квалификации.
                                        0
                                        Потому что это данные которые будут нужны и далее.
                                        Какие тестовые? Накопленные данные о моей работе за период.
                                          0
                                          Потому что это данные которые будут нужны и далее.

                                          Спасибо, кэп. Но зачем?

                                          Какие тестовые? Накопленные данные о моей работе за период.

                                          Вы явно не следите за дискуссией. Вы пишете, что «тестовые данные могут быть оторванными от действительности». Я говорю, что это зависит от квалификации человека, который их готовил. А дальше вы почему-то пишете, что это не тестовые данные, а накопленные данные о вашей работе за период.

                                          Вам не кажется, что где-то произошла подмена?
                                            0
                                            Почему не нужны?
                                            Фраза была к тому что тестовые данные это необходимость, если нет реальных.
                                            Поэтому тестовые данные — далее обсуждать не буду, изначально пост исходит, что данные терять нельзя.
                                              –2
                                              Почему не нужны?

                                              Я не говорю, что они не нужны, я спрашиваю, зачем они кокретному разработчику в конкретном сценарии.

                                              Фраза была к тому что тестовые данные это необходимость, если нет реальных.

                                              Отдел тестирования смеется. Тестовые данные — это необходимость сама по себе, они нужны всегда.

                                              изначально пост исходит, что данные терять нельзя.

                                              Данные нельзя терять почему-то. Это «почему-то» в 99% предполагает еще и SLA. Ваш подход (а) нарушает SLA (б) приводит к потере тех данных, которые могли бы быть записаны в БД в то время, пока она отключена (собственно, это и есть часть SLA)
                                                +1
                                                Задача ПО — сохранение и анализ полученных данных, но разработчику они не нужны? Я не коня в вакууме пишу. Я собираю данные, расширяю поле сбора, и соответственно анализа.

                                                Рад за Ваш, отдел. К теме какое отношение?

                                                Нельзя терять потому что это данные, не тестовые данные, а реальные данные.
                                                  +1
                                                  Задача ПО — сохранение и анализ полученных данных, но разработчику они не нужны?

                                                  Разработчику они не нужны. Разработчику нужны конкретные данные, подходящие под условия задачи, на которых он эту задачу будет решать.

                                                  Нельзя терять потому что это данные, не тестовые данные, а реальные данные.

                                                  Что реальные данные делают в разработческом контуре? Им там не место.
                                                    +2
                                                    Я так понимаю, отсюда и появляется ПО, которое написано для разработчика?

                                                    В данном случае, в БД реальные данные, которые незачем менять виртуальными, и незачем удалять.
                                                      0
                                                      Я так понимаю, отсюда и появляется ПО, которое написано для разработчика?

                                                      Не понимаю вопроса.

                                                      В данном случае, в БД реальные данные, которые незачем менять виртуальными, и незачем удалять.

                                                      Мне кажется, вам уже несколько раз по треду объяснили, чем «реальные» данные хуже «виртуальных», и почему их нельзя использовать в разработческих целях.
                                                        0
                                                        К тому что кто-то пишет по виртуальным данным, а кто-то по реальным.

                                                        Наверное, главное, что вам удобно так вести разработку. Мне так.
                                                          0
                                                          Никто не может гарантировать, что «реальные данные» покроют все возможные тестовые кейсы. А это многовенно убивает качество. (с) К. О.
                                                            0
                                                            Главное копирайт ;)
                                                            0
                                                            Наверное, главное, что вам удобно так вести разработку. Мне так.

                                                            Вы так и не рассказали, как вы гарантируете наличие (и отсутствие) нужных данных для тестирования.
                                                              0
                                                              Гарантировать перед кем чем?
                                                                0
                                                                Перед тестами, очевидно.
                                                                  0
                                                                  Я еще и тесты не пишу.
                                                                    0
                                                                    Речь не о том, пишете ли вы тесты, речь о том, тестирует ли кто-то ваше ПО.
                                                                      0
                                                                      Нет, только используется.
                                                                      Если то что упоминалось, то только мной.
                                                                        0
                                                                        Вот это и объясняет, почему ваше решение не подходит для «обычной разработки ПО».
                        +1
                        Да простой пример.
                        Мне нужен учет времени для отчета заказчикам.
                        Мое ПО собирает эти данные, постоянно.
                        Структура данных расширяется, терять старые нельзя.
                          +1
                          Не понимаю. Учет какого времени? Как какой-то отчет, который вы отдаете заказчикам, не является продуктом, но при этом генерится программой?

                          Не понимаю вашего процесса вообще.
                            +2
                            1. Учет моего времени
                            2. Учет моей программой
                            3. Программа в разработке
                            4. Пользуюсь ей я
                            5. Изменений в структуре — много
                            6. Терять старые данные — нельзя
                            7. Операции по миграции при каждом изменении не подходит.
                              –2
                              Так, давайте уже разбираться.

                              Что делает сама программа? Какова ее бизнес-задача? Какие данные хранятся в БД?
                                +1
                                Не совсем понятно как это относится к посту.
                                Вы считаете, что не бывает БД, которые часто расширяются, нужно обеспечить сохранность данных, и это должно быть просто?

                                По задаче
                                1 уровень — первичка: Сбор времени, какой процесс когда и сколько запущен
                                2ур. обобщение процессов в программы
                                3ур. обобщение программ в группы программ
                                4ур. обобщение групп программ в категории
                                5ур. привязка категорий программ к пользователям
                                … и такая агрегация — может быть бесконечная
                                любой уровень может расширить или усложнить структуру

                                например было строковое поле Workstation стало class Workstation
                                1 этапом добавилось новое поле Workstation
                                новые данные накапливаются по двум схемам
                                2 этапом данные перенесутся со строки в класс
                                3 этапом визуализация перейдет на новую схему
                                между 1 и 2,3 может пройти много времени
                                  –1
                                  Вы считаете, что не бывает БД, которые часто расширяются, нужно обеспечить сохранность данных, и это должно быть просто?

                                  Я считаю, что задача «обеспечить сохранность данных при расширении БД» должна применяться только к продуктивным ландшафтам.
                                    0
                                    С точки зpения банальной эpудиции, каждый индивидуум, кpитически мотивиpующий абстpакцию, не может игноpиpовать кpитеpии утопического субьективизма, концептуально интеpпpетиpуя общепpинятые дефанизиpующие поляpизатоpы. Поэтому консенсус, достигнутый диалектической матеpиальной классификацией всеобщих мотиваций в паpадогматических связях пpедикатов, pешает пpоблему усовеpшенствования фоpмиpующих геотpансплантационных квазипузлистатов всех кинетически коpеллиpующих аспектов.

                                    По-моему, я отвечал, просто и практично.
                                      +1
                                      По-моему, я отвечал, просто и практично.

                                      Что — просто?

                                      Понимаете, ваши проблемы — это признак неправильного процесса разработки. Ну не должно, не должно в разработческом и тестовом (кроме pre-production) ландшафтах быть накопления данных, которые надо мигрировать, там должно быть «хорошо известное состояние» (well-known state).

                                      Соответственно, если у вас в каком-то ландшафте идет накопление данных, то это повод задуматься, а что это за ландшафт? Может быть, это тестовая эксплуатация? Тогда к ней и относиться надо, как к продуктиву, включая миграции и SLA.
                                        0
                                        Это аксиома?
                                          0
                                          Это Best Practice, выработанная на тяжелом опыте. Для того, чтобы ее отвергать, нужно иметь достаточно веские основания.
                                            +1
                                            Чьи Best Practice? Что для Вас веские основания? Я не отвергаю, я утверждаю что Ваш подход к разработке ПО — не единственный.
                                              +1
                                              Чьи Best Practice?

                                              Ну, лично я почерпнул эти правила из Continuous Delivery Хамбра и Фарли, и пока что ни разу в своей практике не видел ситуации, когда они (правила) бы не оправдали себя.

                                              Но вообще правила «начинайте тестирование в предопределенных условиях» — они, как бы, самоочевидны (как бы я ни не любил это слово).

                                              Что для Вас веские основания?

                                              Для начала нужны хотя бы какие-нибудь основания. Выписанные в столбик с указанием минусов и плюсов по каждому решению.

                                              Я не отвергаю, я утверждаю что Ваш подход к разработке ПО — не единственный.

                                              Несомненно. Но надо отдавать себе отчет в том, что он не из воздуха взялся, а основан на конкретных предпосылках.
                                                0
                                                Я данные черпаю из практики.
                                                Для меня определенные условия — реальные условия.
                                                Я не вижу минусов в использовании накопленных данных.
                                                Мое решение тоже не с пустого места появилось.
                                                  –1
                                                  Я не вижу минусов в использовании накопленных данных.

                                                  А как вы делаете тестирование, если у вас неизвестно стартовое состояние?
                                                    0
                                                    По-моему, если данные есть, то состояние, в какой-то мере — известно.
                                                      0
                                                      Угу. На уровне «данные есть». А вот какие они, каков их состав, насколько они корректны с точки зрения бизнеса — вы не знаете.
                                                        0
                                                        Вы знаете?
                                                          0
                                                          Когда использую предопределенный тестовый массив — конечно, знаю.
                                                            0
                                                            На основании чего, приняли решение о моих данных?
                                                              0
                                                              На основании того, что данные, «накапливаемые программой» по определению недетерминированы, иначе их можно было бы сгенерировать. Раз они недетерминированы, значит, мы не можем делать заключений об их корректности или некорректности.
                                                                0
                                                                И?
                                                                  0
                                                                  И вы, по идее, не можете написать конкретные приемочные тесты, если у вас нет точного знания «что в БД есть юзер Вася» и что «у юзера Васи строго один заказ».
                                                                    0
                                                                    Не улавливаю нити.
                                                                    +1
                                                                    … и все. На основании недетерминированных данных очень сложно строить конкретные тесты. Практически невозможно. А уж автоматизированные тесты — тем более.
                                                                      +1
                                                                      Вы все к тестам и тестовым сводите.
                                                                      В БД не тестовые.
                                                                        0
                                                                        Естественно, я все свожу к тестам, потому что какая еще активность может вестись в разработческом контуре? Прототипирование, разработка, тестирование, отладка. Это все упирается в тесты.
                                                                          0
                                                                          Есть еще и индивидуальные разработчики, у которых нет контуров.
                                                                            +1
                                                                            Индивидуальному разработчику, который не может поднять на домашнем компе соответствующие контуры, надо сначала школу закончить.
                                                                              0
                                                                              Хотел минус поставить, но промазал ;)
                                                                              Как закончу школу, Вам первому напишу.
                                                                                0
                                                                                Как будто вы свои программы запускаете в воздухе и у вас на машине не стоит ни СУБД, ни всякие фреймворки ни веб-сервер, если вы веб-разработчик?
                                                                                  0
                                                                                  У меня обычное, рабочее место разработчика.
                                                                              0
                                                                              Есть еще и индивидуальные разработчики, у которых нет контуров.

                                                                              Так не бывает. Даже ваша собственная машина — это ваш разработческий контур.
                                                                                0
                                                                                Неплохо, теперь, наверное, буду лучше писать.
                      0
                      DropCreateAlways — недостаточно. У меня ПО накапливает данные с разных источников. ПО анализирует.
                      Или на голых данных сидеть? Или вручную их заливать каждый раз?
                        0
                        А а если Seed сделать? Сгенерить набор тестовых данных и при Debug конфигурации билда раскатывать данные после миграции.
                          0
                          Зачем генерировать? если их можно собрать, или они уже собраны.
                            0
                            Можно просто в сид скрипт пихнуть. Удобно на тестовых виртуалках раскатывать. В том же тест лабе.
                              0
                              Вот у меня ПО из 2 частей десктоп и веб.
                              Десктоп собирает данные, веб показывает.
                              Десктоп уже за недельку насобирал «первички».
                              Решил поменять/расширить структуру БД:
                              С велосипедом F5 — все обновилось. Копия данных на всякий случай — лежит.
                              Какие манипуляции для скрипта нужны?
                                0
                                ЭЭЭ еще раз. База то где? На десктопе?
                                  0
                                  И база и веб.
                          0
                          ПО накапливает данные с разных источников

                          В продуктиве, правильно?

                          Вот я вам про продуктив и говорю.
                            0
                            Нет, это еще не продукт.
                              0
                              А где еще у вас ПО может проработать недельку?
                                0
                                Чуть выше ответил.
                                И работать оно будет и более
                                И расширятся будет постоянно.
                    0
                    А чем вас бекапы не устроили? Можно даже инкерентированные делать, чтобы сэкономить место и время бекапов. Зачем эти пляски вокруг копирования?
                      0
                      Можно и бэкапами это сделать.
                      Можно и копированием таблиц внутри БД сделать.
                      Тут велосипед можно подправить для себя, код вроде простенький.

                      А копирование:
                      Я не хочу старое держать в рабочей БД.
                      Если старые данные мне не понадобились в течении Х строка — удаляю БД.
                      Я очень ленивый, BackUp-Restore — много действий по сравнению с F5 + Delete когда нужно.
                      +1
                      Все эти миграции имхо работают только на домашних проектах, в реальности это никогда не не работает. Программист должен понимать что он делает, описывать это скриптами ибо большинство изменений ниразу не тривиальны (а некоторые и неоткатываемые без бэкапов). Да и прав на базу может не быть (у нас например DBA занимаются базой, а не разработчики ПО, которое ее использует (да и база 7 ТБ — поди ее покопипасть как аффтор...) )…
                        0
                        А кто мешает описывать изменения через DSL? И часто базы не так сложны, что бы еще DBA держать. Есть другие способы оптимизации.
                          0
                          T-SQL — чем не DSL?
                            0
                            А для какого домена он специфичен?

                            (так и c# можно DSL назвать, только, гм, смысла в этом названии)
                          0
                          Хорошо, предложите свой вариант легкой миграции при разработке ПО когда БД не огромных размеров и есть все права.
                            0
                            Все эти миграции имхо работают только на домашних проектах, в реальности это никогда не не работает.

                            Работает. Ваши «описанные разработчиком скрипты» — это тоже миграция, просто выраженная в другой форме, вот и все.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое