EF Core:域中没有导航 属性 的一对多关系

EF Core: One to many relationship without navigation property in domain

互联网的好朋友们好:)
我正在尝试使用 EF Core 5 实体作为 DDD 意义上的域实体。

我有两个实体的情况,每个实体都有自己的标识(意味着它们是 DDD 对象的实体类型,而不是 ValueObjects):国家和货币。

货币可以属于许多国家(例如欧元)。 另一方面,国家/地区只能有一种 'currently active' 货币,不一定在任何时候都是相同的货币(例如欧盟国家,放弃本国货币而使用欧元)。

在特定的域限界上下文中,我只需要:

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; }
}

我不想在 Currency 实体上使用导航 属性: public ICollection<Country> Countries { get; private set; },只是为了能够定义 1:N 关系,因为这只会污染我的域。

我尝试将导航 属性 添加为 EF 影子 属性,但 EF 不允许这样做,并抛出异常:

The navigation 'Countries' cannot be added to the entity type 'Currency' because there is no corresponding CLR property on the underlying type and navigations properties cannot be added in shadow state.

Currency 不能由 Country 拥有(在 EF 意义上为 OwnsOne),因为这意味着 Currency 必须在数据库中具有复合 {Id, IdCountry} PK,这将违反被能够将一种货币分配给多个国家。

是否有一种解决方案可以在货币和国家/地区之间建立关系(或相反),它不会污染导航域 属性 并允许将相同的 CLR 对象用作域和 EF 实体?

I am trying to use EF Core 5 entities as domain entities in a sense of DDD.

EF 实体表示所谓的 data 模型,它通常不同于 domain/business 模型,有自己的 requirements/modelling 规则,不同于其他模型和导航属性是很好的关系表示,允许在不使用手动连接等的情况下生成不同类型的查询。

所以一般来说,您应该使用单独的模型并在需要的地方在两者之间进行映射,这样就不会“污染”您的领域模式或违反其规则。就像您遵循域模型规则一样,您应该遵循数据模型规则 - 我不明白为什么人们认为 EF 应该遵循他们的规则而不是他们遵循 EF 规则。


无论如何,话虽这么说,但 EF Core 导航属性虽然超级有用,但不是 强制性的(目前通过隐式连接实体和跳过导航的多对多除外) - 你可以两者兼得,只有本金,只有依赖,甚至都没有。

您只需要定义与 正确的 Has / With 对的关系。正确的方法是在存在时使用 pass 导航 属性,在不存在时省略它。

在这种情况下,你可以使用这样的东西:

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> 类 这不太适合关系 - 一种关系有两端)。