Обновить

Модульная модель данных в EF Core

Уровень сложностиСредний
Время на прочтение14 мин
Охват и читатели6.3K
Всего голосов 6: ↑5 и ↓1+6
Комментарии16

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

Я так понимаю, что автор пытается решить фундаментальную проблему распределения модели данных по модулям. Но через code-first. Тут есть ожидаемая проблемка.

Code-first не умеет в совместное владение одной структурой разными модулями.

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

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

Я эту проблему решил совершенно иначе. Следите за руками.

Выносим источник истины (модель данных) из кода в отдельный артефакт. Чтобы EF Core с этим работал, прикручиваем генератор кода. Модель целиковая, на всю БД. Или на все БД (если их несколько). А потом для каждого модуля задаётся видимость через partial generation.

Итого для каждого модуля генерируется свои классы модели, определяя границы видимости данных. Но при этом модель одна.

Самое интересное в поддержке такой схемы работы. Модель одна по определению и изменения вносятся сразу во все модули, которые используют затронутую структуру. И очень просто менять область видимости. Просто наставить нужных чекбоксов и всё.

Code-first не умеет в совместное владение одной структурой разными модулями.

Не понял что это означает, можно с примерами кода?

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

Не понял в чем проблема. Можно пример кода? Что нужно ограничить чтобы такого не было?

Итого для каждого модуля генерируется свои классы модели, определяя границы видимости данных. Но при этом модель одна.

В этой статье модель одна, но разбитая на модули. Модули могут как напрямую инкапсулировать свои данные, так и можно выносить общие куски и ссылаться на них.

Можете привести пример кода что вы имеете ввиду?

Не понял что это означает, можно с примерами кода?

Нельзя. Такие вещи не показываются кодом. Сначала словами и концептуально.

Я же вижу, что вы также видите ограничение code-first и интуитивно пришли к тому же самому решению: вынести модель в отдельный артефакт. Только у меня это описательный формат, а у вас это тот же код с динамической сборкой в единую модель. Ну просто потому что иначе миграции просто не будут работать.

Code-first подход вообще состоит в том, что в коде написана истина, какой должна быть табличка. Тулчейн подхватывает код и всякой чёрной магией вкатывает изменения в реальную БД. Так вот, когда код в разных модулях описывает одну и ту же табличку, то тогда и происходит слом парадигмы.

И мы по сути, решаем одну и ту же проблему. Но немного разными способами. Но с выделением модели в отдельный артефакт, не принадлежащий модулям аппа.

У вас таблица вынесена в отдельный модуль, у меня каждый модуль генерируется заново из общей модели.

Ваш подход требует подключения сборок данных к модулям аппа. При этом модули не могут содержать navigation property, потому что тогда сборки должны перекрёстно друг от друга зависеть. Когда делаешь внешний ключ, ты уже прописываешь в одном классе ссылку на другой. Навегейшен проперти - это ссылка с обратной стороны, на тот класс, который уже гарантированно ссылается на этот. Вот и приехали.

Сколько при этом ещё может возникнуть циклических зависимостей в сложной модели - я даже боюсь себе представить.

У меня есть единая модель. Из которой можно построить необходимое количество dbContext, хоть на каждый модуль отдельный контекст, хоть по два.

Code-first подход вообще состоит в том, что в коде написана истина, какой должна быть табличка. Тулчейн подхватывает код и всякой чёрной магией вкатывает изменения в реальную БД. Так вот, когда код в разных модулях описывает одну и ту же табличку, то тогда и происходит слом парадигмы.


Понял о чем вы говорите. В одной из следующей стей будет описан способ как это преодолеть.

Суть простая: Модуль, который не ссылается на другие модули и реализует IEntityTypeConfiguration<T> объявляется "владельцем T", что записывается в атрибуты сборки, а потом анализатор на уровне хоста проверяет что нет двух модулей-владельцев одного типа.

Ваш подходи интересен, но хотелось бы увидеть код. Без кода сложно судить.

Вы код чего именно хотите увидеть?

Код который описывает модель и код который получается на выходе генератора.

Модель - это XML формат. Код на выходе генератора ничем не отличается от того, что пишут руками в парадигме code-first. При этом генератор - это просто питоний скрипт, который можно править под свои нужды и заморочки своего проекта. Там не на что смотреть. Ну если что, в первом сообщении была ссылка на код, который получается от генератора-примера. Там что-то такое:

[Table("ReleaseFiles")]
public partial class ReleaseFile
{
	[Key]
	public int Id { get; set; }
	public int VersionId { get; set; }
	public int ChannelId { get; set; }
	public string Checksum { get; set; }
	public int FileSize { get; set; }
	public string Filename { get; set; }
	[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
	public DateTime CreatedDate { get; set; }
	public int FileType { get; set; }
	[ForeignKey("VersionId")]
	public Version Version { get; set; }
	[ForeignKey("ChannelId")]
	public ReleaseChannel Channel { get; set; }
}

SQL JOIN

Часто приложение и не должно знать что там джойнится. Ему дают витрину и всё. А как она там внутри БД сделана не аппа дело.

Так можно довольно гибко настраивать внутреннюю структуру БД. Можно и материализованные вьюшки если приложение допускает некоторое отставание данных read model в CQRS

Часто приложение и не должно знать что там джойнится. Ему дают витрину и всё. А как она там внутри БД сделана не аппа дело.

Витрина это что? Представления в БД или другой код?

Нам ведь кроме проекций нужно еще и CRUD делать. Можно конечно убрать проблему на другой слой, но от этого проблема не исчезнет.

Можно и материализованные вьюшки если приложение допускает некоторое отставание данных read model в CQRS

Это все копирование данных, о котором написано в статье, со всеми недостатками.

Поясняю,

CQRS

Read model часто затрагивает несколько доменов, но их связь можно вынести из кода в вмтрины

Write model чаще это один домен

Интересно, но вопрос к использованию IHostingStartup: как-то странно в контексте бд видеть зависимость от asp.

EF Core это часть ASP.NET по факту.

Было на заре .net core, сейчас это уже не так.

Ну формально вроде нет, а фактически EF Core внутри себя опирается на Logger, DI, MemoryCache, а тулы dotnet ef плохо работают без хоста. Просто раньше это все официально было частью ASP.NET, а теперь это какая-то отдельная прослойка, которая вроде как не BCL, распространяется через NuGet, но вроде как и не ASP.NET

Для dotnet ef хост может быть любой, не только web. Ef Core не зависит от пространства Microsoft.AspNetCore, всё что ты перечислил это общие части “нового” dotnet.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации