O'Reilly logo

Programming Entity Framework: Code First by Rowan Miller, Julia Lerman

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Working with Relationships that Have Unidirectional Navigation

So far we have looked at relationships where a navigation property is defined in both classes that are involved in the relationship. However, this isn’t a requirement when working with the Entity Framework.

In your domain, it may be commonplace to navigate from a Destination to its associated Lodging options, but a rarity to navigate from a Lodging to its Destination. Let’s go ahead and remove the Destination property from the Lodging class (Example 4-16).

Example 4-16. Navigation property removed from Lodging class

public class Lodging
{
  public int LodgingId { get; set; }
  public string Name { get; set; }
  public string Owner { get; set; }
  public bool IsResort { get; set; }
  public decimal MilesFromNearestAirport { get; set; }

  public int DestinationId { get; set; }
  //public Destination Destination { get; set; }
  public List<InternetSpecial> InternetSpecials { get; set; }
  public Person PrimaryContact { get; set; }
  public Person SecondaryContact { get; set; }
}

Entity Framework is perfectly happy with this; it has a very clear relationship defined from Lodging to Destination with the Lodgings property in the Destination class. This still causes the model builder to look for a foreign key in the Lodging class and Lodging.DestinationId satisfies the convention.

Now let’s go one step further and remove the foreign key property from the Lodging class, as shown in Example 4-17.

Example 4-17. Foreign key commented out

public class Lodging
{
  public int LodgingId { get; set; }
  public string Name { get; set; }
  public string Owner { get; set; }
  public bool IsResort { get; set; }
  public decimal MilesFromNearestAirport { get; set; }

  //public int DestinationId { get; set; }
  //public Destination Destination { get; set; }
}

Note

Removing DestinationId will break a previous sample, the DeleteDestinationInMemoryAndDbCascade method in Example 4-12. Comment that method out so that your solution will still compile properly.

Remember the Code First convention that will introduce a foreign key if you don’t define one in your class? That same convention still works when only one navigation property is defined in the relationship. Destination still has a property that defines its relationship to Lodging. In Figure 4-13 you can see that a Destination_DestinationId column is added into the Lodgings table. You might recall that the convention for naming the foreign key column was [Navigation Property Name] + [Primary Key Name]. But we no longer have a navigation property on Lodging. If no navigation property is defined on the dependent entity, Code First will use [Principal Type Name] + [Primary Key Name]. In this case, that happens to equate to the same name.

Foreign key added to the database

Figure 4-13. Foreign key added to the database

Note

What if we tried to just define a foreign key and no navigation properties in either class? Entity Framework itself supports this scenario, but Code First does not. Code First requires at least one navigation property to create a relationship. If you remove both navigation properties, Code First will just treat the foreign key property as any other property in the class and will not create a foreign key constraint in the database.

Now let’s change the foreign key property to something that won’t get detected by convention. Let’s use LocationId instead of DestinationId, as shown in Example 4-18. Remember that we have no navigation property; it’s still commented out.

Example 4-18. Foreign key with unconventional name

public class Lodging
{
  public int LodgingId { get; set; }
  public string Name { get; set; }
  public string Owner { get; set; }
  public bool IsResort { get; set; }
  public decimal MilesFromNearestAirport { get; set; }

  public int LocationId { get; set; }
  //public Destination Destination { get; set; }
  public List<InternetSpecial> InternetSpecials { get; set; }
  public Person PrimaryContact { get; set; }
  public Person SecondaryContact { get; set; }
}

Thanks to Destination.Lodgings, Code First knows about the relationship between the two classes. But it cannot find a conventional foreign key. We’ve been down this road before. All we had to do was add some configuration to identify the foreign key.

In previous examples, we placed the ForeignKey annotation on the navigation property in the dependent class or we placed it on the foreign key property and told it which navigation property it belonged to. But we no longer have a navigation property in the dependent class. Fortunately, we can just place the data annotation on the navigation property we do have (Destination.Lodgings). Code First knows that Lodging is the dependent in the relationship, so it will search in that class for the foreign key:

[ForeignKey("LocationId")]
public List<Lodging> Lodgings { get; set; }

The Fluent API also caters to relationships that only have one navigation property. The Has part of the configuration must specify a navigation property, but the With part can be left empty if there is no inverse navigation property. Once you have specified the Has and With sections, you can call the HasForeignKey method you used earlier:

modelBuilder.Entity<Destination>()
  .HasMany(d => d.Lodgings)
  .WithRequired()
  .HasForeignKey(l => l.LocationId);

While a unidirectional relationship may make sense in some scenarios, we want to be able to navigate from a Lodging to its Destination. Go ahead and revert the changes to the Lodging class. Uncomment the Destination property and rename the foreign key property back to DestinationId, as shown in Example 4-19. You’ll also need to remove the ForeignKey annotation from Destination.Lodging and remove the above Fluent API configuration if you added it.

Example 4-19. Lodging class reverted to include navigation property and conventional foreign key

public class Lodging
{
  public int LodgingId { get; set; }
  public string Name { get; set; }
  public string Owner { get; set; }
  public bool IsResort { get; set; }
  public decimal MilesFromNearestAirport { get; set; }

  public int DestinationId { get; set; }
  public Destination Destination { get; set; }
  public List<InternetSpecial> InternetSpecials { get; set; }
  public Person PrimaryContact { get; set; }
  public Person SecondaryContact { get; set; }
}

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required