Entity Framework Core One To Many with No Foreign Key on Master Table

asp.net c# entity-framework-core sql-server

Question

We've decided to consolidate all enums into a RefValue table which I at least have no problem conceiving of, but I can't seem to get Entity to play along.

I think it should make sense once you look at the schema, but the gist of it is that a Person object would have 2 fields when it came to enums (eg gender, relationshipStatus, nameSuffix, etc). One field would store the value (eg "female") and the other would store the id of that RefValue (eg some guid which would be a foreign key to the RefValue table where the value was stored/defined). That way we wouldn't have to do multiple joins on the RefValue table to find those properties values.

Here's the schema:

[Table("Person")]
public class Person : Base
{
    public Guid Id {get; set; }

    public string Name { get; set; }

    public string Gender { get; set; }
    public RefValue GenderRef { get; set; }

    public string RelationshipStatus { get; set; }
    public RefValue RelationshipStatusRef { get; set; }

    public string NameSuffix { get; set; }
    public RefValue NameSuffixRef { get; set; }
}

[Table("RefValue")]
public class RefValue : Base
{
    public Guid Id {get; set; }

    public string Type { get; set; }

    public string Value { get; set; }
}

In the Person object, I'd really just like the RefValue properties to just be Guids as foreign keys to the RefValue table. I started steering towards just making them RefValue properties because it seemed like EFC would make things easier if I did it their way with the concept of navigation properties. The problem though is that it's pretty insistent on having a column on the RefValue table to be the other side of those one-to-many relationships.

I'm fine with adding extra columns on the RefValue table so that is looks more like this:

[Table("RefValue")]
public class RefValue : Base
{
    public string Type { get; set; }

    public string Value { get; set; }

    public ICollection<Person> Genders { get; set; }

    public ICollection<Person> RelationshipStatus { get; set; }

    public ICollection<Person> NameSuffix { get; set; }
}

But no matter how I spin my context class's OnModelCreating() method, I can't seem to get them to play together.

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<RefValue>()
            .HasMany(p => p.Genders)
            .WithOne(p => p.GenderRef)
            .OnDelete(DeleteBehavior.SetNull);
        modelBuilder.Entity<RefValue>()
            .HasMany(p => p.RelationshipStatus)
            .WithOne(p => p.RelationshipStatusRef)
            .OnDelete(DeleteBehavior.SetNull);
        modelBuilder.Entity<RefValue>()
            .HasMany(p => p.NameSuffix)
            .WithOne(p => p.NameSuffixRef)
            .OnDelete(DeleteBehavior.SetNull);
    }

I've tried going the inverse way as well, but I usually get the Introducing FOREIGN KEY constraint ... on table ... may cause cycles or multiple cascade paths

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>()
            .HasOne(p => p.PersonTypeRef)
            .WithMany()
            .OnDelete(DeleteBehavior.SetNull);
        modelBuilder.Entity<Person>()
            .HasOne(p => p.RelationshipStatusRef)
            .WithMany()
            .OnDelete(DeleteBehavior.SetNull);
        modelBuilder.Entity<Person>()
            .HasOne(p => p.NameSuffixRef)
            .WithMany()
            .OnDelete(DeleteBehavior.SetNull);
    }

Is there any way to create this kind of one-to-many (or really many-to-one) kind of relationship without the cascading problem or (and especially) entity creating shadow properties on the non-dependent table?

1
2
4/13/2018 9:27:24 PM

Popular Answer

Let's take your original entity model:

[Table("Person")]
public class Person : Base
{
    public Guid Id {get; set; }

    public string Name { get; set; }

    public string Gender { get; set; }
    public RefValue GenderRef { get; set; }

    public string RelationshipStatus { get; set; }
    public RefValue RelationshipStatusRef { get; set; }

    public string NameSuffix { get; set; }
    public RefValue NameSuffixRef { get; set; }
}

[Table("RefValue")]
public class RefValue : Base
{
    public Guid Id {get; set; }

    public string Type { get; set; }

    public string Value { get; set; }
}

There is absolutely no problem to configure unidirectional (i.e. with navigation property only at one of the sides) FK relationships using the fluent API. The only thing you need to consider is that because of the multiple cascade paths introduced by such model, you can't use delete cascade options, i.e. you should specify DeleteBehavior.Restrict.

With that being said, here is the fluent configuration for the above model:

modelBuilder.Entity<Person>()
    .HasOne(p => p.GenderRef)
    .WithMany()
    .OnDelete(DeleteBehavior.Restrict);

modelBuilder.Entity<Person>()
    .HasOne(p => p.RelationshipStatusRef)
    .WithMany()
    .OnDelete(DeleteBehavior.Restrict);

modelBuilder.Entity<Person>()
    .HasOne(p => p.NameSuffixRef)
    .WithMany()
    .OnDelete(DeleteBehavior.Restrict);

And in case you add collection navigation properties, don't forget to specify them in the corresponding WithMany calls, otherwise EF Core will create additional relationships.

2
4/16/2018 5:00:11 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow