Найти в Дзене
Архитектура на .NET

Как в EF Core работают миграции и зачем нужен ModelSnapshot

EF Core при использовании миграций генерирует несколько разных файлов, в которых легко запутаться. Особенно много вопросов возникает при совместной разработке, когда в разных ветках кода создаются разные миграции. Например:
Чтобы ответить на эти вопросы, достаточно разобраться, как именно работает механизм миграций. Добавление очередной миграции приводит к следующим изменениям в проекте: На самом деле с точки зрения классов, для каждой миграции создаётся только один новый класс, и он наследуется от базового класса Migration. Просто этот файл с помощью ключевого слова partial разбит на два файла. Меньше всего вопросов вызывает файл Migration.cs, т.к. в нём описаны методы Up() и Down(), и именно с ними сталкивается разработчик чаще всего. Файл Migration.designer.cs содержит вторую часть того же класса-наследника Migration, и в него выносится переопределение метода BuildTargetModel и атрибут [Migration]. Метод BuildTargetModel содержит описание полной модели данных, учитывающее изменения

EF Core при использовании миграций генерирует несколько разных файлов, в которых легко запутаться. Особенно много вопросов возникает при совместной разработке, когда в разных ветках кода создаются разные миграции. Например:

  1. Важен ли порядок применения миграций?
  2. Что делать, если один разработчик накатил миграцию из своей ветки, а другой создал миграцию с более ранней датой, которая ещё не применена?
  3. Как мержить изменения в файле DbContextModelSnapshot.cs?


Чтобы ответить на эти вопросы, достаточно разобраться, как именно работает механизм миграций.

Добавление очередной миграции приводит к следующим изменениям в проекте:

  1. Добавляется файл Migration.cs (в реальности он будет иметь название {TIMESTAMP}_{MIGRATIONNAME}.cs)
  2. Добавляется файл Migration.designer.cs
  3. Изменяется файл DbContextModelSnapshot.cs

На самом деле с точки зрения классов, для каждой миграции создаётся только один новый класс, и он наследуется от базового класса Migration. Просто этот файл с помощью ключевого слова partial разбит на два файла.

Меньше всего вопросов вызывает файл Migration.cs, т.к. в нём описаны методы Up() и Down(), и именно с ними сталкивается разработчик чаще всего.

Файл Migration.designer.cs содержит вторую часть того же класса-наследника Migration, и в него выносится переопределение метода BuildTargetModel и атрибут [Migration]. Метод BuildTargetModel содержит описание полной модели данных, учитывающее изменения, вносимые текущией миграцией.

В файле DbContextModelSnapshot.cs так же содержится полная модель данных. Только, в отличие от файла Migration.designer.cs, который остаётся неизменным, этот файл всегда содержит последнюю версию модели данных, т.е. с учётом всех существующих миграций, и обновляется после добавления каждой из них. Именно описанная в этом файле модель используется для вычисления очередной дельты при создании новой миграции.

Важно понимать вот что:

  1. При добавлении очередной миграции EF Core НЕ смотрит в реальную БД и НЕ получает её схему. Он вычисляет изменения в модели основываясь ТОЛЬКО на данных из файла DbContextModelSnapshot.cs.
  2. Когда миграция применяется, для модификации схемы данных в реальной БД используются ТОЛЬКО методы Up/Down, т.е. код из Migration.designer.cs для этого не используется.

Таким образом, файлы .designer.cs по сути представляют собой историю изменений файла DbContextModelSnapshot.cs, и служат исключительно для этого: вернуться к ним, если вдруг что-то пойдёт не так. Самим EF Core в процессе применения миграций и создания новых миграций они не используются.

Файл DbContextModelSnapshot.cs, напротив, служит единственным источником правды о текущем состоянии БД. Таким образом, если в разных ветках кода были одновременно созданы разные миграции, этот файл после мержа обязан содержать изменения из обеих веток.

Последовательность применения миграций

Каждая миграция содержит timestamp как в названии файлов, так и в атрибуте [Migration] в файле .designer.cs.

Название файлов влияет только на порядок отображения в Solution Explorer, для EF Core оно не имеет значения. Идентификатором миграции для EF Core служит содержимое атрибута [Migration]. Именно оно попадает в колонку MigrationId таблицы __EFMigrationsHistory.

При обновлении БД EF Core получает ВСЕ строки из таблицы __EFMigrationsHistory и применяет те миграции, которых нет в таблице, сортируя их по id (значению атрибута [Migration()]).