How can I define a 1:0 relationship using Entity Framework Core 2.1?

ef-core-2.1 ef-core-2.2 entity-framework-core

Question

Updating entity Persons I get the following error:

System.InvalidOperationException: The property 'ID' on entity type 'Person' has a temporary value. Either set a permanent value explicitly or ensure that the database is configured to generate values for this property.
   at Microsoft.EntityFrameworkCore....

This has been discussed several places, and a similar issue was reported fixed by the EF Core team. However, one of those posts is about multiple updates to an entity and the one-to-many solution does not work here; additionally, I cannot make one ID column nullable and prefer to use the fluent API configuration. The documentation example does not work either, so I am asking here.

The scenario is that I am upgrading a legacy ASP.NET MVC 4 project to ASP.NET MVC Core, and as a result I am upgrading from EF 6.1 to EF Core 2.1. I will happily move to 2.2 instead if it solves this problem; I think it was still in prerelease when I started.

Here is a (ridiculously) simplified version of my entities:

public class Person
{
    public int ID { get; set; }
    public string name { get; set; }
    public virtual Worker Worker { get; set; }
}

public class Worker
{
    public int ID { get; set; }
    public string somePersonalDetails { get; set; }
    public virtual Person Person { get; set; }
    // other relationships exist
}

I am using fluent API configuration:

public class PersonBuilder
{
    public PersonBuilder(EntityTypeBuilder<Person> entity)
    {
        entity.HasKey(k => k.ID);
        entity.HasOne(p => p.Worker)
            .WithOne(p => p.Person)
            .HasForeignKey<Person>(p => p.ID)
            //.IsRequired(false) //?
            .OnDelete(DeleteBehavior.Cascade);
    }
}

public class WorkerBuilder
{
    public WorkerBuilder(EntityTypeBuilder<Worker> entity)
    {
        entity.HasKey(k => k.ID);
        // other relationships are defined
    }
}

public override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.Configurations<Person>().Add(typeof(PersonBuilder));
    builder.Configurations<Worker>().Add(typeof(WorkerBuilder));
}

The reason that it's split apart like that is because I adapted it from our leftover EF 4/5/6 configuration. Yay legacy code. Nevertheless it works (for other defined types). The way I am reading that, it says "define a foreign key on the related Worker object pointing to the ID of this object." It does just the opposite.

I have tried:

  1. Defining the key relationship on the WorkerBuilder type instead. This yields SQLite Error 19: 'FOREIGN KEY constraint failed'. Amazingly, however, it still attempts to define the key on the Person entity, which is wrong.

  2. Removing some of the specific expressions in hopes that EF will just figure out the relationship itself. It doesn't; if I provide too little information, it tries to use columns|fields that don't exist (e.g., "PersonID", or is unable to figure out the relationship altogether.

So, I am stumped. Has anyone done this successfully? In plain English,

"A person may or may not have a worker record" (1:0); and, "if they have a worker record, both records have the same ID." (FK_W_ID__P_ID)

1
1
12/31/2018 8:17:21 AM

Accepted Answer

The EF6 setup is creating a so called One-To-One Shared Primary Key Association where the PK of the dependent entity is also a FK to principal entity.

The things have changed in EF Core. It supports naturally both shared PK and FK one-to-one associations. Also optional/required are not used to determine the principal and dependent ends of the association. IsRequired is used to control if the dependent entity can exists w/o principal and applies only whith separate FK. While HasForeignKey and HasPrincipalKey are used to determine the principal and dependent ends of the association and also map the dependent FK and principal PK / Alternate Key.

With that being said, the equivalent EFC configuration is as follows:

builder.HasOne(ag_ch => ag_ch.Code)
    .WithOne(c => c.AggregationChild)
    .HasForeignKey<AggregationChildren>(ag_ch => ag_ch.AggregationChildrenId)
    .OnDelete(DeleteBehavior.Restrict);

So we start with defining the relationship using HasOne + WithOne.

Then HasForeignKey<AggregationChildren>(ag_ch => ag_ch.AggregationChildrenId) to tell EF that (1) AggregationChildren is the dependent end of the relationship, and (2) that the PK AggregationChildrenId should also be used as FK.

Finally, OnDelete(DeleteBehavior.Restrict) is the EFC equivalent of the EF6 WillCascadeOnDelete(false). The other options like ClientSetNull and SetNull apply only when the dependent has a separate optional FK, which is not the case with shared PK association.

Reference: Relationships

16
9/27/2017 9:33:13 AM


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