#c# #entity-framework-core #domain-driven-design
Вопрос:
Привет, добрые люди Интернета 🙂
Я пытаюсь использовать сущности EF Core 5 в качестве доменных сущностей в смысле DDD.
У меня есть случай двух сущностей, каждая из которых имеет свою собственную идентичность (это означает, что они относятся к типу объектов DDD, а не к объектам ValueObjects): Страна и валюта.
Валюта может принадлежать многим странам (например, евро). Страна, с другой стороны, может иметь только одну «действующую в настоящее время» валюту, которая не обязательно всегда является одной и той же валютой (таким примером может быть страна ЕС, отказавшаяся от своей собственной национальной валюты в пользу евро).
В конкретном ограниченном доменом контексте мне понадобилось бы только:
public class Country : Entity<Guid>
{
public string Name { get; private set; }
// the rest is omitted for readability
public Currency Currency { get; private set; }
}
и
public class Currency : Entity<Guid>
{
public string Name { get; set; }
public string Code { get; set; }
}
Я не хочу иметь свойство навигации: public ICollection<Country> Countries { get; private set; }
в сущности валюты, просто чтобы иметь возможность определять отношения 1:N, потому что это только загрязнило бы мой домен.
Я попытался добавить свойство навигации в качестве свойства EF shadow, но EF не разрешает этого, за исключением:
Навигация «Страны» не может быть добавлена к типу сущности «Валюта», поскольку в базовом типе отсутствует соответствующее свойство CLR, а свойства навигации не могут быть добавлены в теневое состояние.
Валюта не может принадлежать (в смысле EF как OwnsOne) стране, потому что это означало бы, что валюта должна иметь составной {Идентификатор, идентификатор страны} PK в базе данных, что нарушило бы требование о возможности присвоения одной валюты нескольким странам.
Существует ли решение для установления связи между валютой и страной (или наоборот), которое не загрязняет домен свойством навигации и позволяет использовать один и тот же объект CLR в качестве домена и сущности EF?
Ответ №1:
Я пытаюсь использовать сущности EF Core 5 в качестве доменных сущностей в смысле DDD.
Сущности EF представляют так называемую модель данных, которая в целом отличается от предметной области/бизнес-модели, имеет свои собственные требования/правила моделирования, которые отличаются от других моделей, а свойства навигации представляют собой отличное представление отношений, которое позволяет создавать различные типы запросов без использования ручных соединений и т. Д.
Поэтому, как правило, вы должны использовать отдельные модели и сопоставлять их там, где это необходимо, чтобы не «загрязнять» режим вашего домена или не нарушать его правила. Просто так же, как вы следуете правилам модели домена, вы должны следовать правилам модели данных — я не понимаю, почему люди думают, что EF должен следовать их правилам, а не правилам EF.
В любом случае, с учетом сказанного, несмотря на то, что основные навигационные свойства EF очень полезны, они не являются обязательными (за исключением в настоящее время для многих-ко-многим с помощью неявной сущности соединения и пропусков навигации) — у вас может быть и то, и другое, просто принципал, просто зависимый или даже ни один из концов.
Все, что вам нужно, это определить отношения с подходящей Has
/ With
парой. Правильно означает использовать свойство передачи навигации, когда оно существует, и опускать его, когда его нет.
В этом случае вы могли бы использовать что-то вроде этого:
modelBuilder.Entity<Country>()
.HasOne(e => e.Currency) // reference navigation property
.WithMany() // no collection navigation property
.IsRequired(); // omit this for optional relationship (to allow null FK)
Того же можно добиться, если запустить настройку с другой стороны. Только в этом случае вы должны явно указать аргумент универсального типа, поскольку он не может быть выведен автоматически:
modelBuilder.Entity<Currency>()
.HasMany<Country>() // no collection navigation property
.HasOne(e => e.Currency) // reference navigation property
.IsRequired(); // omit this for optional relationship (to allow null FK)
Вы можете использовать любой способ, просто не делайте оба, так как это одно и то же отношение, поэтому его следует настраивать только один раз, чтобы избежать конфликтующих конфигураций (в случае, если вы используете отдельные IEnityTypeConfiguration<TEntity>
классы, которые на самом деле плохо сочетаются с отношениями — одно отношение с двумя концами).
Комментарии:
1. Я протестировал этот случай с EF Core 5, и в моей настройке код из вопроса также работает без какой-либо конфигурации Fluent. br
2. Спасибо. @ИванСтоев. Похоже, я слишком усложнил конфигурацию типа сущности с помощью:
public void Configure(EntityTypeBuilder<Currency> entity) { entity.Property<ICollection<Country>>("Countries"); }
и:public void Configure(EntityTypeBuilder<Country> entity) { entity.HasOne(e => e.Currency).WithMany("Countries").HasForeignKey(e => e.Id).IsRequired(false); }
3. И КСТАТИ: использование сущности EF в домене вместо отдельного объекта домена: я согласен. У нас есть такой подход, но сейчас я провожу исследование, как упростить нашу архитектуру. Даже с помощью AutoMapper это в большинстве случаев более или менее бесполезное сопоставление между DTO -> доменностью ->> DBO (или сущностью EF). Другая причина-отслеживание изменений и т. Д… Вы что-то получаете, что-то теряете при том или ином подходе.