ErikMuir
ErikMuir

Reputation: 181

Conditional navigation properties in Entity Framework Core

I'm trying to create a navigation property in EF Core that would setup its reference conditionally based off the values of two properties. I'm not sure if this is even possible.

Let me show you an example: let's say I have a hierarchical structure of entities, such as Country, State, County, and City. I also have an entity called Law, which could be "owned" by any of the hierarchical entities.

So, I create this enum:

public enum OwnerType
{
    Country,
    State,
    County,
    City
}

...and the Law class:

public class Law
{
    public int Id { get; set; }
    public OwnerType OwnerType { get; set; }
    public int OwnerId { get; set; }
}

Now I want to setup the Law class to have a navigation property that would link OwnerId to the Primary Key of the corresponding entity based off of the OwnerType value.

I considered adding this to the Law class:

public virtual object Owner { get; set; }

Or to create an IOwner interface that each of the hierarchical entities would implement and then I'd add this instead:

public virtual IOwner Owner { get; set; }

But then I have no idea how to setup the EntityTypeConfiguration with the EntityTypeBuilder. This obviously won't work:

builder.HasOne(x => x.Owner).WithMany(x => x.Laws).HasForeignKey(x => x.OwnerId);

I really just have no idea how to accomplish what I'm trying to do here. Any ideas?

Upvotes: 3

Views: 1262

Answers (1)

Nozim Turakulov
Nozim Turakulov

Reputation: 879

As I can see you have 4 different relations and you want to handle them with one foreign key what's a bad idea in concept. If you have 4 relations - you need to have 4 FKs.

In pure OOP you could use and IOwner interface but Entity Framework requires explicit information to map your relations respectively and I believe that's the best way. Just add 4 different nullable FKs and validate Law state with OwnerType value.

public class Law {
    public int Id { get; set; }
    public OwnerType OwnerType { get; set; }

    [ForeignKey(nameof(Country)]
    public int? CountryId { get; set; }
    public Country Country { get; set; }

    [ForeignKey(nameof(State)]
    public int? StateId { get; set; }
    public State State { get; set; }

    [ForeignKey(nameof(County)]
    public int? CountyId { get; set; }
    public County County { get; set; }

    [ForeignKey(nameof(City)]
    public int? CityId { get; set; }
    public City City { get; set; }

    private void Validate() {
        switch (OwnerType)
        {
            case OwnerType.Coutnry:
                if(CountryId == null)
                    throw new LawValidationException("Country is requried");
            break;
            case OwnerType.State:
                if(StateId == null)
                    throw new LawValidationException("State is requried");
            break;
            case OwnerType.County:
                if(CountyId == null)
                    throw new LawValidationException("County is requried");
            break;
            case OwnerType.City:
                if(CityId == null)
                    throw new LawValidationException("City is requried");
            break;
            default:
                    throw new LawValidationException("Invalid law owner type");
        }
    }
}

This approach solves your problem, perfectly fits to Entity Framework abilities and can be easily integrated into external logic including unit tests.

Upvotes: 1

Related Questions