1

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)

Chaim Eliyah
  • 2,743
  • 4
  • 24
  • 37
  • 1
    Please check my answer and follow it thoroughly. Hope it will solve your problem! – TanvirArjel Dec 31 '18 at 08:38
  • 1
    You are reading it wrong. In "plain English", what you wrote is "in the Person - Worker relation, use the Person ID property as a FK to Worker". And you want the opposite `.HasForeignKey(w => w.ID)` – Ivan Stoev Dec 31 '18 at 09:13
  • To be crystal clear, the correct configuration of the desired relationship in the posted code is to change `.HasForeignKey(p => p.ID)` to `.HasForeignKey(w => w.ID)`. Now the relationship will be the way you want it. If you have issues with add/update operation, post another question including the failing code (mcve). – Ivan Stoev Dec 31 '18 at 11:43
  • For me, this doesn't change anything. All four possible combinations still place a foreign key on the parent table pointing to the ID of the child. – Chaim Eliyah Dec 31 '18 at 12:12
  • re: doing something wrong: I literally have tried all the combinations numerous times. https://github.com/chaim1221/machete-stub/blob/4da54e3893f0f32ee57f52a323ce5ec019919c84/Machete.Data/MacheteContext.cs#L165 – Chaim Eliyah Dec 31 '18 at 12:17
  • Here is the original, working, code: https://github.com/chaim1221/Machete/blob/master/Machete.Data/MacheteContext.cs#L143 – Chaim Eliyah Dec 31 '18 at 12:22

1 Answers1

0

Write your model classes as follows:

public class Person
{
    public int PersonId { get; set; }
    public string Name { get; set; }

    public virtual Worker Worker { get; set; }
}

public class Worker
{
    public int PersonId { get; set; }
    public string somePersonalDetails { get; set; }
    public virtual Person Person { get; set; }

    // other relationships exist
}

Then in the PersonConfiguration and WorkerConfiguration:

public class PersonConfiguration : IEntityTypeConfiguration<Person>
{
    public void Configure(EntityTypeBuilder<Person> builder)
    {
       builder.HasKey(u => u.PersonId);
    }
}

public class WorkerConfiguration : IEntityTypeConfiguration<Worker>
{
    public void Configure(EntityTypeBuilder<Worker> builder)
    {
       builder.HasKey(u => u.PersonId);
       builder.HasOne(u => u.Person)
           .WithOne(b => b.Worker)
           .HasForeignKey<Worker>(b => b.PersonId);
    }
}

Then in the OnModelCreating of DbContext:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.ApplyConfiguration(new PersonConfiguration());
    modelBuilder.ApplyConfiguration(new WorkerConfiguration());
}
TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
  • I will try it. I may have left out an important detail, which is that `Person` and `Worker` both inherit from `Record`, which defines field `ID` for both of them. Does that matter here? – Chaim Eliyah Dec 31 '18 at 08:41
  • First try yourself accordingly. Make the necessary changes according to your code. If not works then add that details to the question. May be some necessary changes have to be made! – TanvirArjel Dec 31 '18 at 08:43
  • Also you put `IEntityTypeConfiguration` as the generic type for the `WorkerConfiguration`; I'm going to assume you meant `IEntityTypeConfiguration`? – Chaim Eliyah Dec 31 '18 at 08:50
  • Thank you for noticing! Yes! It is `IEntityTypeConfiguration` – TanvirArjel Dec 31 '18 at 08:51
  • Result: `SQLite Error 19: 'FOREIGN KEY constraint failed'.` And the FK is still on the wrong table. DX -- I have to sleep, will check in AM (PST). – Chaim Eliyah Dec 31 '18 at 08:59
  • 1
    Use full version of sql – TanvirArjel Dec 31 '18 at 09:00
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/185970/discussion-between-tanvirarjel-and-chaim-eliyah). – TanvirArjel Dec 31 '18 at 09:03