0

My use case is pretty much identical to what I see in the tutorials: I have a class Slachtoffer and another class Schadeberekening. Each Schadeberekening must have a Slachtoffer, and a Slachtoffer can have a Schadeberekening.

I am trying to set up the database using code-first like this:

public class Slachtoffer
{
    public int Id { get; set; }

    public int? SchadeberekeningId{ get; set; }
    public Schadeberekening? Schadeberekening { get; set; }
}

public class Schadeberekening
{
    public int Id { get; set; }

    public int SlachtofferId { get; set; }
    public Slachtoffer Slachtoffer { get; set; }
}

modelBuilder.Entity<Slachtoffer>()
             .HasOne(s => s.Schadeberekening)
             .WithOne(ad => ad.Slachtoffer)
             .HasForeignKey<Schadeberekening>(ad => ad.SlachtofferId);

modelBuilder.Entity<Schadeberekening>()
             .HasOne<Slachtoffer>(a => a.Slachtoffer)
             .WithOne(sa => sa.Schadeberekening)
             .HasForeignKey<Slachtoffer>(sa => sa.SchadeberekeningId);

Yet when I look at the created tables, only in the Slachtoffer table, the PK from the Schadeberekening is considered as foreign key. In the Schadeberekening table, it is just an "int".

How can I fix this?

enter image description here

browser

controller

Zegher V
  • 117
  • 7

2 Answers2

1

In the Schadeberekening, it is just an "int". How can I fix this?

I have a suggestion like below, remove the code in OnModelCreating, and modify your code like:

Slachtoffer:

public class Slachtoffer
    {
        public int Id { get; set; }

        [ForeignKey("Schadeberekening")]
        public int? SchadeberekeningId { get; set; }
        public virtual Schadeberekening? Schadeberekening { get; set; }
    }

Schadeberekening:

public class Schadeberekening
    {
        public int Id { get; set; }

        [ForeignKey("Slachtoffer")]
        public int SlachtofferId { get; set; }

        public virtual Slachtoffer Slachtoffer { get; set; }
    }

result:

enter image description here

Qing Guo
  • 6,041
  • 1
  • 2
  • 10
  • That does indeed work, but I had hoped I would now be able to update both the Slachtoffer in Schadeberekening and the Schadeberekening in Slachtoffer entities now that the foreign keys were properly set. But I guess that that will maybe never work with entity framework? – Zegher V Apr 20 '23 at 19:56
  • 1
    Hi @ZegherV That's for entity-framework-core, if you want for entity framework you can have a look at [One to one optional relationship using Entity Framework Fluent API](https://stackoverflow.com/questions/18240362/one-to-one-optional-relationship-using-entity-framework-fluent-api). – Qing Guo Apr 21 '23 at 01:37
  • Yes sorry I meant for entity-framework-core. So I can now add a Slachtoffer to a Schadeberekening, but when I try to perform an edit or delete from the Slachtoffer's views, the SchadeberekeningsID I provide, does not result in a Schadeberekening in my Slachtoffer's entity because my Model State is invalid (Schadeberekening is null). Perhaps I am not supposed to be able to do it from both ways? – Zegher V Apr 21 '23 at 21:48
  • 1
    @ZegherV What do you mean " Model State is invalid(Schadeberekening is null)" ? My (Schadeberekening is null) but the Model State is valid. Beacuse `public virtual Schadeberekening? Schadeberekening { get; set; }` Already add `?`. you can see the picture: https://i.stack.imgur.com/Or2ex.gif – Qing Guo Apr 22 '23 at 02:00
  • yes, the "Slachtoffer" key is invalid in the ModelState of my CREATE in Schadeberekeningscontroller. I added a screenshot to my main post! Come to think of it, perhaps it is invalid because I am providing a Schadeberekening that has no linked Slachtoffer yet, whereas each Schadeberekening needs a Slachtoffer? – Zegher V Apr 22 '23 at 09:29
  • 1
    @ZegherV Ok, I see your picture, try to remove `enable` from your project file (double-click the project name or right-click the project to choose Edit Project File). Then ModelState.IsValid will be true. – Qing Guo Apr 22 '23 at 09:35
  • 1
    @ZegherV "whereas each Schadeberekening needs a Slachtoffer?" no need . Because in the database, we don't create the Slachtoffer column. Have a look at [this](https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/adding-model?view=aspnetcore-6.0&tabs=visual-studio#build-the-app) . – Qing Guo Apr 22 '23 at 09:39
  • 1
    Removing enable did the trick, thank you so much! All this time I thought I was configuring my relationship wrong, but it was just the modelstate... Will removing this "nullable" have any side effects? – Zegher V Apr 22 '23 at 13:11
  • 1
    @ZegherV Sorry, I miss it. "Will removing this "nullable" have any side effects?" yes, it will, when you don't add `[Required]` to a property , it can be null, so if you don't want to add `[Required]` to not null, don't remove the `enable` , just add `?` to Slachtoffer like: `public virtual Slachtoffer? Slachtoffer { get; set; }` – Qing Guo Apr 23 '23 at 01:44
1

One to one relationships in EF can be configured in two ways. The default, and best enforceable way is to join the two tables on their PKs. You would choose one entity as the Root for the relationship, and the related table would share that Id serving as both the PK and the FK back to the root. The relationship can still be nullable (1-to-0-or-1):

public class Slachtoffer
{
    public int Id { get; set; }

    public Schadeberekening? Schadeberekening { get; set; }
}

public class Schadeberekening
{
    [Key, ForeignHey(nameof(Slachtoffer))]
    public int Id { get; set; }

    public virtual Slachtoffer Slachtoffer { get; set; }
}

Note that there are no FK fields in either table, the Schadeberekening associated to a Slachtoffer will share the same ID. If you are explicitly managing the key setup, the Id in the Slachtoffer would be treated as an Identity, while in the Schadeberekening it should not be, as Slachtoffer will be responsible for controlling that ID.

The second way to configure a 1-to-1 is using an explicit FK. This would only exist on one table or the other, not both. So for example:

public class Slachtoffer
{
    public int Id { get; set; }

    public int? Schadeberekening { get; set; }
    public Schadeberekening? Schadeberekening { get; set; }
}

public class Schadeberekening
{
    public int Id { get; set; }

    public virtual Slachtoffer Slachtoffer { get; set; }
}

In this case you would need to tell EF about the 1-to-1 relationship and that it should locate the FK in the Slachtoffer table using the prescribed FK column. (This ideally should be set up as a Shadow Property in the entity)

If configured as a 1-to-1 relationship, EF will do its best to respect that constraint, but at the database level this is a many-to-one relationship unless you add a unique constraint on Slacktoffer.SchadeberekeningId. (Code-first may do this as part of the HasOne().WithOne()) You don't have another FK in the secondary table to go back to the parent since that creates the potential to have broken and invalid relationships. For instance if I have A referencing B with a reference back to A, If I change A's reference to C, it is possible that B's reference back to A does not get removed, or C to A does not get created.

A good breakdown of options for creating one-to-one relationships can be found here: https://learn.microsoft.com/en-us/ef/core/modeling/relationships/one-to-one

Steve Py
  • 26,149
  • 3
  • 25
  • 43
  • Ok, I think the "required one-to-one" applies to this situation then. However, what is the purpose of the "virtual" that you added in your code? Their code also contains "public Blog Blog { get; set; } = null!; ", would you recommend this "=null"? – Zegher V Apr 20 '23 at 08:17
  • 1
    `virtual` is a carry-over for lazy loading if you want to enable that, it isn't necessary if you're expecting to eager load or use projections, I just default to using it as a fail safe as it's better to lazy load than end up with a null ref. exception IMO. Initializing Blog to #null isn't required, but they opted to do that with the null forgiveness (!) to keep the compiler warnings happy for C# 10. Another option is to add a protected empty constructor /w a #pragma to ignore non-nullable initialization. – Steve Py Apr 20 '23 at 09:11