0

I'd like to do something similar to EF Code First 0..1 to 0..1 relationship but in EF Core rather than EF 5.

Essentially I want the following relationships, given these models, without using a junction table

class foo {
    public int FooId { get; set; }
    public string FooName { get; set; }
    public int Foo_BarId { get; set; }
}
class bar {
    public int BarId { get; set; }
    public string BarName { get; set; }
    public int Bar_FooId { get; set; }
}

Why is it not possible to do something like the following?

Foo to bar and bar to foo Note, FooId and BarId are Primary Keys [PK] and I'd like to set a FK relationship from Foo_BarId to BarId and similarly and FK relationship from Bar_FooId to FooId. Essentially, Foo_BarId and Foo_BarId can be null while FooId and BarId is required.

The resulting table for foo and bar would look like (i used a picture of html because its too hard to post html rendered stuff here)

enter image description here

Why is this not allowed? Or if it is, what are the magic commands for builder.HasOne.WithOne?

John
  • 85
  • 7
  • What would be the purpose of this relationship? Do you have a real world scenario? – Alex Nov 17 '20 at 03:05
  • I have a main account (Main) and a third-party account (TP). There can only be one main account associated with one third party account (and vice versa). This is the one-to-one part. I also want to create a main account without creating a third party account (and vice versa). This is the zero or one [0..1] to [1] part (and vice versa making [0..1] to [0..1]). A junction table implies it's a many-to-many relationship. I dont want to associate Main to many third parties (or vice versa) nor do I want to allow future developers to do so. – John Nov 17 '20 at 03:38
  • Why not use this approach? https://www.learnentityframeworkcore.com/configuration/one-to-one-relationship-configuration – Alex Nov 17 '20 at 03:46
  • Thats 1 to 1 i need [0..1] to [0..1] – John Nov 17 '20 at 03:57
  • You can easily make it 0 to 1. – Alex Nov 17 '20 at 04:16
  • Please show me how to make it 0 to 1, hence the question: "Or if it is, what are the magic commands for builder.HasOne.WithOne?" – John Nov 17 '20 at 04:29
  • https://stackoverflow.com/questions/54985032/ef-core-one-to-one-or-zero-relationship – Alex Nov 17 '20 at 04:47
  • A junction table is the only way to get this right because it's the only way to prevent at the database level that foo1 refers to bar1, while bar1 refers to foo2. In the junction table there can be 2 unique indexes on both foreign keys which makes the junction 0..1:0..1. See my answer [here](https://stackoverflow.com/a/54533596/861716), but also the comment below it on how these relationships, when implemented *per se*, defy logic. – Gert Arnold Nov 17 '20 at 21:09

1 Answers1

1

You specify a Bar_FooId value of 22 in two records in the Bar table. This is 1-to-many, not 1-to-1, from Foo to Bar. The 1 to [0..1] constraints will be violated.

A 1 to [0..1] relationship can use an unique index filtered for NOT NULL on the right. The filter allows more than one record with NULL for the "0" part of the relationship. Except for these NULLs, the values on the right are unique.

The index on the right side of a 1-to-many relationship can't be unique.

The other relationship, so far, is not 1-to-many. But it's too soon to tell.

EDIT:

Okay, I had it backwards. These relationships need to be in place and the filtered indexes are desired because you don't want the 1-to-many. Here is a total WAG as to what I'd do. I expect it to be wrong because I'm not a EF person.

class foo {
    public int FooId { get; set; }
    public string FooName { get; set; }
    [ForeignKey("BarId")] 
    public int Foo_BarId { get; set; }
}
class bar {
    public int BarId { get; set; }
    public string BarName { get; set; }
    [ForeignKey("FooId")] 
    public int Bar_FooId { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Bar>()
        .HasKey(z => z.BarID)
        .HasOne(a => a.Foo)
        .WithOne(b => b.Bar)
        .HasForeignKey<Bar>(b => b.Bar_FooId);

    modelBuilder.Entity<Foo>()
        .HasKey(z => z.FooID)
        .HasOne(a => a.Bar)
        .WithOne(b => b.Foo)
        .HasForeignKey<Foo>(b => b.Foo_BarId);
}

Check the database to insure you have the filtered unique indexes required. They are supported if adding manually according to this link. The filter on NULL is included automatically if .IsUnique() is specified. (You have to remove it if you don't want it filtered.)

https://learn.microsoft.com/en-us/ef/core/modeling/indexes

I'm old fashioned. This is easier to see in T-SQL for me. Feel free to mark me down for the blunder(s).

Randy in Marin
  • 1,108
  • 4
  • 9
  • `This is 1-to-many, not 1-to-1, from Foo to Bar. The 1 to [0..1] constraints will be violated. `
    \n \n Yes, see the comment next to the 4th row of **bar** table (NOT SETTABLE....) so yes I want the database (or Entity framework) to throw an exception when someone tries to create an entry that looks like row 4.
    – John Nov 18 '20 at 19:51
  • Okay, sorry about the confusion. I will amend my answer, – Randy in Marin Nov 19 '20 at 00:47
  • Thanks for trying but I can immediately see your code not being able to compile, for instance HasKey() has a semicolon and then a .HasOne right after it. I've had multiple attempts at getting this right so I can show you how close I got, if you'd like to see it. – John Nov 19 '20 at 01:50
  • The foreign keys must be nullable (`int?`), otherwise the relationships can never be optional. How are you going to enforce that both foreign keys always point to the correct objects and not `foo1 → bar1 → foo2`? Or `foo1 → bar1` without a FK value in `bar1`. – Gert Arnold Nov 19 '20 at 07:58
  • @John, it was a feeble attempt. As I said, I'm not a EF coder. From what I read, it appears possible. – Randy in Marin Nov 20 '20 at 00:26
  • @GertArnold, each table can have a PK. Either table can have a non-PK column that's a FK to the others PK. The PK index is unique and non-null. The FK index is unique and filtered to exclude nulls so that as many nulls as needed can be used. Easy enough in T-SQL. I suppose it can be easy in EF too once it's figured out the first time. – Randy in Marin Nov 20 '20 at 00:33
  • @John, I removed the extra ";", but that does not mean it's good. I might be adding insult to injury by straying from SQL. – Randy in Marin Nov 20 '20 at 00:39
  • @RandyinMarin The FKs aren't nullable. Anyway. As for the point of stray references, this can also be done by just one nullable FK. But then one entity must be designated as the "principal", i.e. not really a principal that has to exist, but the entity that, quite arbitrarily, has the FK field. – Gert Arnold Nov 20 '20 at 07:36