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

1

8

Hi good people of Internet :)
I am trying to use EF Core 5 entities as domain entities in a sense of DDD.

I have a case of two entities, each with their own identity (meaning they are Entity type of DDD objects, not ValueObjects): Country and Currency.

Currency can belong to many Countries (for example EUR). Country, on the other hand, can have only one 'currently active' Currency, which isn't necessarily the same Currency at all times (such example would be EU country, abandoning their own national currency for the EUR).

In a specific domain bounded context, I would need only:

public class Country : Entity<Guid>
{
    public string Name { get; private set; }
    // the rest is omitted for readability
    public Currency Currency { get; private set; }
}

and

public class Currency : Entity<Guid>
{
    public string Name { get; set; }
    public string Code { get; set; }
}

I don't want to have navigation property: public ICollection<Country> Countries { get; private set; } on the Currency entity, just to be able to define 1:N relationship, because this would only pollute my domain.

I tried to add navigation property as EF shadow property, but EF doesn't allow it, with thrown exception:

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 can't be owned (in a EF sense as OwnsOne) by Country, because that would mean that Currency would have to have composite {Id, IdCountry} PK in the database, which would violate the requirement of being able to assign one currency to multiple countries.

Is there a solution to establish a relationship between Currency and Country (or the other way around), which doesn't pollute the domain with navigation property and allows the same CLR object to be used as a domain and EF entity?

Aft answered 7/7, 2021 at 11:22 Comment(0)
P
10

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

EF entities represent the so called data model, which in general is different from domain/business model, has its own requirements/modelling rules which are different from the other models, and navigation properties are great relationship representation which allow producing different type of queries without using manual joins etc.

So in general you should use separate models and map between the two where needed, thus not "polluting" your domain mode or breaking its rules. Simply the same way you follow the domain model rules, you should follow the data model rules - I don't understand why the people think EF should follow their rules rather than they following EF rules.


Anyway, with that being said, while super useful, EF Core navigation properties are not mandatory (except currently for many-to-many via implicit junction entity and skip navigations) - you can have both, just principal, just dependent or even neither ends.

All you need is to define the relationship with the proper Has / With pair. Proper means to use pass the navigation property when exists, and omit it when it doesn't.

It this case, you could use something like this:

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)

The same can be achieved if you start the configuration from the other side. Just in that case you have to provide explicitly the generic type argument since it cannot be inferred automatically:

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)

You can use either way, just don't do both, since it is one and the same relationship, hence should be configured only once to avoid conflicting configurations (in case one is using separate IEnityTypeConfiguration<TEntity> classes which don't really fit well with relationships - one relationship with two ends).

Pigment answered 7/7, 2021 at 12:10 Comment(3)
I tested this case with EF Core 5, and in my setup, code from question is working without any Fluent config as well. BRSupplemental
Thank you. @IvanStoev. It looks like I overcomplicated entity type configuration with: public void Configure(EntityTypeBuilder<Currency> entity) { entity.Property<ICollection<Country>>("Countries"); } and: public void Configure(EntityTypeBuilder<Country> entity) { entity.HasOne(e => e.Currency).WithMany("Countries").HasForeignKey(e => e.Id).IsRequired(false); }Aft
And BTW: using EF entity in domain instead of separate domain object: I agree. We have this approach, but now I am doing a research how to simplify our architecture. Even with AutoMapper it is most of the time more or less useless mapping between DTO -> DomainEntity -> DBO (or EF entity). Other reason is change tracking etc... You get something, you lose something with one approach or the other.Aft

© 2022 - 2024 — McMap. All rights reserved.