Рекомендуемый способ очистки старых миграций Entity Framework Core

После разработки нашего приложения некоторое время мы накопили довольно много миграций базы данных EFCore. Поскольку EFCore добавляет моментальный снимок всей модели базы данных при каждой миграции, этот код добавляет довольно много. После анализа около 80% нашего времени компиляции уходит на миграции (компиляция + анализаторы Roslyn).

Итак, пришло время очистить некоторые старые миграции! Но как лучше всего это сделать? Официальных указаний по этому поводу вроде нет...

Нам не нужны никакие откаты (мы только откатываемся вперед), так что все упрощается. Нам нужна поддержка создания базы данных с нуля и обновления базы данных из последних нескольких миграций.

Что я пробовал:

  1. Ядерный вариант, по-видимому, заключается в удалении всех миграций и моментального снимка модели и создании новой начальной миграции. Хотя это нормально, это кажется немного опасным. При таком подходе нам нужно быть очень осторожными, чтобы каждая часть схемы базы данных была частью модели кода. Один пограничный случай, с которым мы, например, столкнулись, заключается в том, что EFCore еще не поддерживает проверенные ограничения. Поэтому мы добавили проверенное ограничение в миграцию, но не в модель кода. Таким образом, при создании новой начальной миграции проверенное ограничение не было ее частью.

  2. В качестве эксперимента я попытался удалить снимок модели из всех старых миграций, так как снимки составляют 90% кода, что вызывает длительное время компиляции. Я понял, что EFCore использует моментальный снимок только в качестве инструмента сравнения для новой миграции. Однако после удаления моментального снимка старые миграции больше не выполнялись, когда они запускались в новой базе данных.

Так есть ли лучший способ выполнить то, что я хочу?

Странно, согласно это, решение 2 должно было сработать.

Nicky Muller 28.06.2019 10:55

Только что проверил, работает ли решение 2 на EFCore 2.0. Столкнулся с той же проблемой, которую я описал (свежая БД не выполняет миграцию без файла Designer).

Nicky Muller 28.06.2019 11:11

Я создал задачу в репозитории EFCore для решения 2: github.com/aspnet/EntityFrameworkCore/issues/16331

Nicky Muller 28.06.2019 11:19

Первое решение работает для меня, но да, вы упомянули хороший момент, когда это может быть опасно.

Artyom Ignatovich 03.07.2019 22:16
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
15
4
5 844
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Хорошо, с тех пор как я задал этот вопрос, я немного поэкспериментировал с этим.

На данный момент кажется, что лучший способ сделать это — вариант 1. Вариант 2 был бы намного лучше, но пока не будет реализован эта функция EFCore, это не совсем выполнимо для моего варианта использования (поддержка существующих баз данных с миграциями на них и поддержка пустых баз данных) .

Вариант 1 также имеет несколько подводных камней, на которые я наткнулся (может быть, даже больше, на которые я не наткнулся). Итак, вот как я это сделал:

Создайте новую начальную миграцию:

  1. Убедитесь, что все ваши существующие миграции были применены к вашей базе данных. Мы создадим новую начальную миграцию, поэтому миграции, которые не были применены, будут потеряны.
  2. Удалите старые файлы миграции EFCore и файл моментального снимка базы данных.
  3. Создайте новую начальную миграцию из текущего состояния вашей базы данных. (Например, через dotnet ef migrations add Initial-PostCleanup.)

Эта новая миграция совместима только с новыми базами данных, поскольку она создаст все таблицы (и произойдет сбой, если какая-либо из таблиц, ограничений и т. д. уже существует). Итак, теперь мы собираемся сделать эту миграцию совместимой с существующей базой данных:

  1. Создайте сценарий SQL для новой начальной миграции через dotnet ef migrations script -o script.sql.
  2. Удалите первую транзакцию (до первой GO), которая создает __EFMigrationsHistory таблицу:
IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL
BEGIN
    CREATE TABLE [__EFMigrationsHistory] (
        [MigrationId] nvarchar(150) NOT NULL,
        [ProductVersion] nvarchar(32) NOT NULL,
        CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
    );
END;

GO
  1. Удалите последнюю транзакцию, которая вставляет новую запись в таблицу __EFMigrationsHistory:
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20190704144924_Initial-PostCleanup', N'2.2.4-servicing-10062');

GO
  1. Удалите команды GO, так как мы поместим скрипт создания в оператор IF:
    Замените GO\r\n\r\n ничем.
  2. Теперь откройте файл миграции (файл C#, а не файл sql) и замените метод Up следующим:
protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.Sql(@"
DECLARE @migrationsCount INT = (SELECT COUNT(*) FROM [dbo].[__EFMigrationsHistory])
IF @migrationsCount = 0
BEGIN
    % PASTE YOUR EDITED SQL SCRIPT HERE %
END
");
}

Сделанный! Теперь все должно работать!

Обязательно сравните схему базы данных, а также данные до и после новой базы данных. Все, что не является частью, если ваша модель EF Code не является частью новой базы данных.

Работает отлично, обязательно повторно добавьте пользовательские скрипты из старых миграций. Например, мне пришлось добавить код для создания представления, которое в противном случае не существовало бы при создании новой базы данных.

Jonas Marty 31.07.2019 11:33

Возможно, при использовании этого решения есть одна ловушка: вы потеряете независимость от базы данных, поскольку скопированный SQL теперь специфичен для вашего типа базы данных.

Jonas Marty 31.07.2019 11:36

Любая причина, по которой нельзя было бы просто а.) удалить миграции б.) создать начальную и в.) удалить все, кроме первого элемента в __migrationhistory в таблице базы данных, и обновить первую запись, чтобы она имела правильное имя и хэш, как указано в снимок?

Barry 19.08.2021 05:30

@Barry Вы имеете в виду делать только эти 3 вещи вместо всех вещей, которые я описал в ответе? Или вы имеете в виду что-то более конкретное?

Nicky Muller 19.08.2021 10:11

Да точно, просто интересно. Поскольку это работает для меня, я не был уверен, что это фатально оставляет что-то за кадром; такие как индексы, ограничения и т.д.? Заранее спасибо! очень ценю ответ

Barry 19.08.2021 18:11

Ну, не тестировал, и это было некоторое время. Но главная проблема заключалась в том, чтобы убедиться, что новая миграция не пытается воссоздать таблицы, если они уже существуют. Я предполагаю, что вы выполняете шаг c вручную в БД, а не через миграцию, верно? Если это нормально для вас, то ваше решение кажется идеальным. У меня было ограничение, что я не мог (и не хотел) изменять производственную базу данных вручную.

Nicky Muller 19.08.2021 19:09

Немного поздно, но у нас была такая же проблема в нашем текущем проекте. Более 400 миграций и 6 миллионов строк кода внутри .Designer. Вот как нам удалось решить эту проблему:

MigrationProject.csproj

  <PropertyGroup>
     ...
     <DefaultItemExcludes Condition = "'$(Configuration)' == 'Debug' ">$(DefaultItemExcludes);Migrations\**\*.Designer.cs</DefaultItemExcludes>
  </PropertyGroup>

Таким образом, вам не нужно ни сбрасывать миграцию в чистое состояние, ни удалять файлы .Designer. Вы всегда можете изменить конфигурацию на Release и использовать файлы .Designer любыми средствами.

Да, это то, что нужно дать вам некоторое время, реальное решение проблемы — это «перезагрузка», которую мы просто не можем пропустить.

Jaime Yule 12.06.2020 18:22

Если вы хотите сохранить только последние файлы конструктора миграции, см. мой ответ: stackoverflow.com/a/65004969/331281

Dejan 25.11.2020 13:45

Чтобы сбросить все миграции и обновления с нуля (предположим, что на диске нет полезных данных), могут быть полезны следующие шаги.


(1) Убедитесь, что файл program.cs не оптимизирован для создания/обновления базы данных с помощью команды Database.EnsureCreate, так как эта команда предотвращает миграцию. (2) Удалить папку Миграции.
(3) сборка дотнета
(4) обновление базы данных dotnet ef 0 -c yourContextFile
(5) миграция dotnet ef добавляет init -c yourContextFile
(6) обновление базы данных dotnet ef -c yourContextFile

Другие вопросы по теме