В моем приложении, использующем EF Core 8.0.7 с подходом «сначала код», у меня есть таблица Content
, содержащая содержимое приложения на основе языка. Таблица Content
ссылается на записи в родительских объектах через ParentId
, причем каждая запись Content
уникально идентифицируется комбинацией Key
, ParentId
и LanguageId
.
Я хочу добавить свойство навигации ко всем моделям сущностей, имеющим связанный контент. В частности, я хочу, чтобы свойство навигации выглядело так:
public virtual ICollection<Content>? Contents { get; set; }
Вот мой класс модели Content
:
public class Content : EntityBase
{
[MaxLength(40)]
public string Key { get; set; }
[MaxLength(2000)]
public string Value { get; set; }
public Guid ParentId { get; set; }
public Guid LanguageId { get; set; }
[MaxLength(40)]
public string ParentTable { get; set; }
public virtual Language? Language { get; set; }
}
Учитывая, что EF Core управляет свойствами навигации через внешние ключи, как мне настроить эти отношения? Должен ли я настроить свойство навигации определенным образом, чтобы гарантировать, что записи Content
правильно связаны с их родительскими объектами? Существуют ли какие-либо конкретные конфигурации или соглашения EF Core, которым мне следует следовать?
Примечание. Мне известны похожие сообщения по этой теме, но они не решили мою проблему.
Короткий ответ: Нет. Избегайте этого деномализованного дизайна. Хотя это выглядит просто и компактно, вы не можете использовать отношения FK между одной таблицей содержимого и другими связанными таблицами, и, аналогично, база данных также не может обеспечить ссылочную целостность. Это также худший вариант с учетом отсутствия или сложной индексации и, что более важно, того факта, что все записи контента помещаются в одну таблицу. Если у вас есть 1 миллион строк контента по 10 связанным объектам, запросы будут более производительными с выделенными FK и индексами по отдельным таблицам контента (100 тысяч здесь, 250 тысяч там, 25 тысяч для типа C и т. д.), чем постоянный запрос по 1 миллиону строк.
Лучше:
public abstract class Content : EntityBase
{
[MaxLength(40)]
public string Key { get; set; }
[MaxLength(2000)]
public string Value { get; set; }
public Guid LanguageId { get; set; }
[MaxLength(40)]
public virtual Language? Language { get; set; }
}
public class CompanyContent : Content
{
public virtual Company Company { get; set; }
}
public class Company : EntityBase
{
// ...
public virtual ICollection<CompanyContent> Contents { get; } = [];
}
Использование модели наследования TPC или TPT. Я склоняюсь к TPC для более простых и быстрых объединений, но TPT потенциально был бы более полезен, если вам необходимо выполнять запросы ко всему контенту, независимо от родства.
Не имеет значения, что в итоге вы получите в 10 раз больше таблиц, где каждая таблица отличается только от FK до поддерживаемого ею типа. «Цена» базы данных — это дополнительные определения таблиц, но стоимость хранения данных для общего количества строк остается прежней. Запрос содержимого для любой таблицы меньшего размера не предполагает опрос всего набора содержимого, а только строк содержимого, относящихся к этой таблице.
Большое спасибо, после того, как я углублюсь в это, я воспользуюсь этим подходом.