3

I have the following construct; a person with an address (ownsone, sub-entity) and an address with a country (hasone, one-to-one)

public class Person
{
     public Guid Id {get; set;}
     public string Name {get; set;}
     public Address Address {get; set;}
}

public class Address 
{
     public Guid Id {get; set;}
     public Guid CountryId {get; set;}
     public Country Country {get; set;}
}

public class Country
{
     public Guid Id {get; set;}
     public string CountryCode {get; set;}
}

public class EntityConfiguration<Person> where Person : class
{
     public void Configure(EntityBuilder<Person> builder)
     {
         builder.OwnsOne(p => p.Address, addressBuilder => 
         addressBuilder.HasOne(address => address.Country).WithOne();
     }
}

When I run Add-Migration I get a block of code for each entity which owns one address. With auto-generated keys and so on. But I want to specify the relation explicitly with HasForeignKey. How to do this?

  • I think first thing you need to do is declare the foreign objects as virtual so for example Address in your person class becomes `public virtual Address Address {get;set;}` – bilpor Dec 08 '17 at 08:37
  • also just noticed. Your address class has `guid CountryId` declared, but in the country class it's just Id. It would be better to declare your Id's as {classname}Id to make it clear and unambiguous which Id's you are referencing – bilpor Dec 08 '17 at 09:09
  • Actually `CountryId` property in `Address` class in enough for EFC to recognize it by convention as FK corresponding to `Country` navigation property. So the relationship *is* indeed created with foreign key (`Person.Address_CountryId` -> `Country.Id`). What's the issue? – Ivan Stoev Dec 08 '17 at 09:12
  • @bilpor, why should the property be marked as virtual? – Wilko van der Veen Dec 08 '17 at 09:13
  • 1
    @WilkovanderVeen virtual because : It allows the Entity Framework to create a proxy around the virtual property so that the property can support lazy loading and more efficient change tracking – bilpor Dec 08 '17 at 09:17
  • @IvanStoev, my question was not clear. I meant explicitly instead of specifically, so no 'secret hidden' assumptions from entity framework – Wilko van der Veen Dec 08 '17 at 09:17
  • I see. Just append `.HasForeignKey
    (address => address.CountryId)` after `.WithOne()`
    – Ivan Stoev Dec 08 '17 at 09:19
  • @bilpor This doesn't apply to EF Core (currently). See [navigation property should be virtual - not required in ef core?](https://stackoverflow.com/questions/41881169/navigation-property-should-be-virtual-not-required-in-ef-core/41881299#41881299). – Ivan Stoev Dec 08 '17 at 09:21
  • @IvanStoev Ah yes, sorry i didn't notice the .Net core tag – bilpor Dec 08 '17 at 09:25
  • 1
    @bilpor: **I strongly disagree**. `myCountry.CountryId` is a case of [Smurf naming convention](https://blog.codinghorror.com/new-programming-jargon/). `myCountry.Id` is perfectly fine and avoids duplicate names. Similarly, I would even suggest to rename `CountryCode` to `Code`. Prepending your classname to all of your class' properties is an exercise in futility. _" to make it clear and unambiguous which Id's you are referencing"_ => This is already unambiguous if you know the type of the variable (e.g. `myCountry`) you're referencing. – Flater Dec 08 '17 at 09:36
  • 1
    How sure are you that this is indeed a one-on-one relationship between `Country` and `Address`? A one-to-many makes a lot more sense (many addresses can refer to the same country); and the current definition of your entity _classes_ is consistent with a one-to-many relationship. Are you really expecting to add the same countrycode **multiple** times, if you e.g. have multiple addresses in a particular country? What would be the benefit of doing so? – Flater Dec 08 '17 at 09:41
  • @Flater NO..I dont agree with the analogy of the smurf naming convention. You have to think of the bigger picture and what's going on under the hood at the database end. Just add SQL Profiler for example and take a look at the SQL that has been built by EF in both cases. – bilpor Dec 08 '17 at 09:44
  • @bilpor: The same is true of the database, where the `Id` column is logically contained in the `Countries` table. I don't see any issue there. And if you're referring to ambiguous `Id` columns when joining tables, that's a non-issue. [Table aliases have existed for a long, long time.](http://www.dofactory.com/sql/alias). E.g. `SELECT * FROM Countries c INNER JOIN Adresses a on a.CountryId = c.Id Where a.Id = 123`. Notice that there's no ambiguity between `c.Id` and `a.Id`. – Flater Dec 08 '17 at 09:49
  • @bilpor: Entity framework uses table aliases (`[Extent1]`, `[Extent2]`) by default. It doesn't even check beforehand if there's a conflicting column or not. Two entities having a property with the same name is not a problem. Peppering your classnames into your property names is not a good solution. – Flater Dec 08 '17 at 09:58
  • @flater, it is indeed a one-to-many relation. But the problem stays the same for a one-to-one relation. I will change my example. – Wilko van der Veen Dec 08 '17 at 10:54

1 Answers1

6

EF Core provides 2 fluent APIs for one-to-one relationships - HasForeignKey and HasPrincipalKey.

The main difference with one-to-many APIs is that you need to explicitly provide the generic type argument, because the principal and dependent end of the relationship cannot be determined by the HasOne / WithOne calls (for one-to-many the one is always the principal and many is the dependent). The navigation properties in this case doesn't matter:

addressBuilder.HasOne(address => address.Country)
   .WithOne()
   .HasForeignKey<Address>(address => address.CountryId)

Reference: Relationships

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343