Efcore: HasOptional method from EF6.x analog for One-to-Zero or One relation

Created on 16 Jun 2017  路  2Comments  路  Source: dotnet/efcore

I have 2 table: person and person_address. If for person exists address, i have row in person_address (i don't have additional column in person for describe this). This is one - to - zero or one relation how I understand.

Can I configure this type of relation in ef core? And if i can't, would be this added in future?

```c#
public class Person
{
public int Id { get; set; }

public string Name { get; set; }

public virtual PersonAddress Address { get; set; }

}

public class PersonAddress
{
public int PersonId { get; set; }

public string Street { get; set; }

... // some othe fields

}

// and using in some code like this

var query = _context.Persons.Include(p => p.Address)
.Where(...).ToList();


I expected SQL Query like this:

```sql
SELECT p.*, pa.*
FROM dbo.Person p
LEFT JOIN dbo.PersonAddress pa ON (p.Id = pa.PersonId)

If I try configure relation like this:

```c#
personBuilder
.HasOne(p => p.Address)
.WithOne()
.HasPrincipalKey(pa => pa.PersonId)
.IsRequired(false);

It's required additional PersonAddress_Id nullable column in Person

```sql
SELECT p.Id, p.*, pa.*
FROM dbo.Person p
LEFT JOIN dbo.PersonAddress pa ON (p.PersonAddress_Id = pa.PersonId)

If I try configure like this:

c# personBuilder .HasOne(p => p.Address) .WithOne() .HasForeignKey<Person>(p => p>Id) .HasPrincipalKey<PersonAddress>(pa => pa.PersonId) .IsRequired(false);

It's throw Exception about wrong configuration, because IsRequired(false) mark Id column as nullable, whats's wrong, because it is Key.

closed-question

Most helpful comment

In EFCore, all principal to dependent navigation are optional. So in your case Person may not have corresponding row in PersonAddress table making it one to zero/one relationship though we call just one-to-one.

With model like above, by convention can create a perfect one-to-one relation because Person has reference navigation to PersonAddress and PersonAddress has property named PersonId which is what would be the name of FK property by convention if PersonAddress was dependent side. So EF creates a one-to-one relationship with Person as principal, PersonAddress as depedent & PersonAddress.PersonId as fk property referencing PK of Person which is Person.Id. This gives you model and query as you want.

Since there is no Id or PersonAddressId property in PersonAddress (conventionally those are the names of PK we look for), you need to configure PK for PersonAddress manually (or add property like mentioned). Code for that would be
modelBuilder.Entity<PersonAddress>().HasKey(e => e.PersonId)
Here I made PersonId PK too. It can be any other property too. It will have no impact on the relationship you are looking for.

With above extra added code in OnModelCreating EF conventions will create expected model. But if you want to specify the relationship explicitly this is how it is written
C# modelBuilder.Entity<Person>() .HasOne(e => e.PersonAddress) .WithOne() .HasForeignKey<PersonAddress>(e => e.PersonId)

Configuration you tried to use,
In case 1, you configured PersonAddress.PersonId as Principal Key making PersonAddress principal side and Person as dependent. Since there is no matching FK in Person, EF created one in shadow state for you. Since you also configured the relationship as IsRequired(false) that shadow FK is nullable. So you got extra nullable column named PersonAddressId in Person table. For Shadow FK nullable is default so you don't need to configure IsRequired as false.

In case 2, again like case1 you configured PersonAddress as principal & Person as dependent. Additionally, PersonAddress.PersonId is configured as principal key & Person.Id is foreign key. Which is totally inverse of what is expected. Further when you configure IsRequired(false) which says relationship is optional which is also equivalent of saying foreign key can have null values will try to set FK -> Person.Id as nullable but since it is PK for Person table, it cannot be nullable. Hence it throws exception.

I believe this should answer all your questions. If there are further questions or if I made mistake in understanding the expected model, then feel free to reply.

All 2 comments

In EFCore, all principal to dependent navigation are optional. So in your case Person may not have corresponding row in PersonAddress table making it one to zero/one relationship though we call just one-to-one.

With model like above, by convention can create a perfect one-to-one relation because Person has reference navigation to PersonAddress and PersonAddress has property named PersonId which is what would be the name of FK property by convention if PersonAddress was dependent side. So EF creates a one-to-one relationship with Person as principal, PersonAddress as depedent & PersonAddress.PersonId as fk property referencing PK of Person which is Person.Id. This gives you model and query as you want.

Since there is no Id or PersonAddressId property in PersonAddress (conventionally those are the names of PK we look for), you need to configure PK for PersonAddress manually (or add property like mentioned). Code for that would be
modelBuilder.Entity<PersonAddress>().HasKey(e => e.PersonId)
Here I made PersonId PK too. It can be any other property too. It will have no impact on the relationship you are looking for.

With above extra added code in OnModelCreating EF conventions will create expected model. But if you want to specify the relationship explicitly this is how it is written
C# modelBuilder.Entity<Person>() .HasOne(e => e.PersonAddress) .WithOne() .HasForeignKey<PersonAddress>(e => e.PersonId)

Configuration you tried to use,
In case 1, you configured PersonAddress.PersonId as Principal Key making PersonAddress principal side and Person as dependent. Since there is no matching FK in Person, EF created one in shadow state for you. Since you also configured the relationship as IsRequired(false) that shadow FK is nullable. So you got extra nullable column named PersonAddressId in Person table. For Shadow FK nullable is default so you don't need to configure IsRequired as false.

In case 2, again like case1 you configured PersonAddress as principal & Person as dependent. Additionally, PersonAddress.PersonId is configured as principal key & Person.Id is foreign key. Which is totally inverse of what is expected. Further when you configure IsRequired(false) which says relationship is optional which is also equivalent of saying foreign key can have null values will try to set FK -> Person.Id as nullable but since it is PK for Person table, it cannot be nullable. Hence it throws exception.

I believe this should answer all your questions. If there are further questions or if I made mistake in understanding the expected model, then feel free to reply.

Thanks for explanation. Now i think it's more clear than methods in EF 6.0

Was this page helpful?
0 / 5 - 0 ratings