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

Chapter 4. Using Convention and Configuration for Relationships

In Chapter 3, you learned about convention and configuration that affect attributes of properties and the effects that these have on the database. In this chapter, the focus will be on convention and configuration that affects the relationships between classes. This includes how classes relate to one another in memory, as well as the corresponding foreign key constraints in the database. You’ll learn about controlling multiplicity, whether or not a relationship is required, and working with cascade deletes. You’ll see the conventional behavior and learn how to control the relationships using Data Annotations and the Fluent API.

You’ll start seeing more configuration that can be performed with the Fluent API but cannot be done through Data Annotations. Recall, however, that if you really love to apply configuration with attributes, the note in Mapping to Non-Unicode Database Types points to a blog post that demonstrates how to create attributes to perform configuration that is only available through the Fluent API.

You’ve already seen some of the relationship conventions in action throughout the earlier chapters of this book. You built a Destination class (Example 4-1) that has a Lodgings property which is a List<Lodging>.

Example 4-1. The Destination class with a property that points to the Lodging class
public class Destination
{
  public int DestinationId { get; set; }
  public string Name { get; set; }
  public string Country { get; set; }
  public string Description { get; set; }
  public byte[] Photo { get; set; }

  public List<Lodging> Lodgings { get; set; }
}

On the other end of the relationship, the Lodging class (Example 4-2) has a Destination property that represents a single Destination instance.

Example 4-2. The Lodging class with its reference back to the Destination 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 Destination Destination { get; set; }
}

Code First sees that you have defined both a reference and a collection navigation property, so by convention it will configure this as a one-to-many relationship. Based on this, Code First can also determine that Lodging is the dependent end of the relationship (the end with the foreign key) and Destination is the principal end (the end with the primary key). It therefore knows that the table Lodging maps to will need a foreign key pointing back to the primary key of Destination. You saw this played out in Chapter 2, where it created the Destination_DestinationId foreign key field in the Lodgings table.

In the rest of this chapter, you will get an understanding of the full set of conventions that Code First has around relationships and how to override those conventions when they don’t align with your intent.

Working with Multiplicity

As you’ve seen, Code First will create relationships when it sees navigation properties and, optionally, foreign key properties. Details about those navigation properties and foreign keys will help the conventions determine multiplicity of each end. We’ll focus on foreign keys a little later in this chapter; for now, let’s take a look at relationships where there is no foreign key property defined in your class.

Code First applies a set of rules to work out the multiplicity of each relationship. The rules use the navigation properties you defined in your classes to determine multiplicity. There can either be a pair of navigation properties that point to each other (bidirectional relationship) or a single navigation property (unidirectional relationship):

  • If your classes contain a reference and a collection navigation property, Code First assumes a one-to-many relationship.

  • Code First will also assume a one-to-many relationship if your classes include a navigation property on only one side of the relationship (i.e., either the collection or the reference, but not both).

  • If your classes include two collection properties, Code First will use a many-to-many relationship by default.

  • If your classes include two reference properties, Code First will assume a one-to-one relationship.

  • In the case of one-to-one relationships, you will need to provide some additional information so that Code First knows which entity is the principal and which is the dependent. You’ll see this in action a little later on in this chapter, in the Working with One-to-One Relationships section. If no foreign key property is defined in your classes, Code First will assume the relationship is optional (i.e., the one end of the relationship is actually zero-or-one as opposed to exactly-one).

  • In the Working with Foreign Keys section of this chapter, you will see that when you define a foreign key property in your classes, Code First uses the nullability of that property to determine if the relationship is required or optional.

Looking back at the Lodging to Destination relationship that we just revisited, you can see these rules in action. Having a collection and a reference property meant that Code First assumed it was a one-to-many relationship. We can also see that, by convention, Code First has configured it as an optional relationship. But in our scenario it really doesn’t make sense to have a Lodging that doesn’t belong to a Destination. So let’s take a look at how we can make this a required relationship.

Configuring Multiplicity with Data Annotations

Most of the multiplicity configuration needs to be done using the Fluent API. But we can use Data Annotations to specify that a relationship is required. This is as simple as placing the Required annotation on the reference property that you want to be required. Modify Lodging by adding the Required annotation to the Destination property (Example 4-3).

Example 4-3. Required annotation added to Destination property
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; }

  [Required]
  public Destination Destination { get; set; }
}

If you were to run the application so that the database gets recreated with the change you just made, you would see that the Destination_DestinationId column in the Lodgings table no longer allows null values (Figure 4-1). This is because the relationship is now required.

Lodgings table with required foreign key
Figure 4-1. Lodgings table with required foreign key

Configuring Multiplicity with the Fluent API

Configuring relationships with the Fluent API can look confusing if you haven’t taken the time to understand the fundamental ideas. We’ll lead you down the path to enlightenment.

When fixing relationships with Data Annotations, you apply annotations directly to the navigation properties. It’s very different with the Fluent API, where you are literally configuring the relationship, not a property. In order to do so, you must first identify the relationship. Sometimes it’s enough to mention one end, but most often you need to describe the complete relationship.

To identify a relationship, you point to its navigation properties. Regardless of which end you begin with, this is the pattern:

Entity.Has[Multiplicity](Property).With[Multiplicity](Property)

The multiplicity can be Optional (a property that can have a single instance or be null), Required (a property that must have a single instance), or Many (a property with a collection of a single type).

The Has methods are as follows:

  • HasOptional

  • HasRequired

  • HasMany

In most cases you will follow the Has method with one of the following With methods:

  • WithOptional

  • WithRequired

  • WithMany

Example 4-4 shows a concrete example using the existing one-to-many relationship between Destination and Lodging. This configuration doesn’t really do anything, because it is configuring exactly what Code First detected by convention. Later in this chapter, you will see that this approach is used to identify a relationship so that you can perform further configuration related to foreign keys and cascade delete.

Example 4-4. Specifying an optional one-to-many relationship
modelBuilder.Entity<Destination>()
  .HasMany(d => d.Lodgings)
  .WithOptional(l => l.Destination);

Example 4-5 shows what this same configuration would look like inside an EntityTypeConfiguration class rather than directly inside OnModelCreating.

Example 4-5. Specifying a relationship in an EntityConfiguration class
HasMany(d => d.Lodgings)
  .WithOptional(l => l.Destination);

This identifies a relationship that Destination Has. It has a Many relationship that is defined by its property, Lodgings. And the Lodgings end of the relationship comes along With a relationship (which is Optional) to Destination. Figure 4-2 attempts to help you visualize this relationship the way the model builder sees it.

How Entity Framework perceives a one- to-many relationship
Figure 4-2. How Entity Framework perceives a one- to-many relationship

We looked at how to change this to be a required relationship with Data Annotations, so now let’s see how to do the same with the Fluent API. Add the configuration shown in Example 4-6 to your DestinationConfiguration class.

Example 4-6. Configuring a required relationship with the Fluent API
HasMany(d => d.Lodgings)
  .WithRequired(l => l.Destination);

This looks very similar to the configuration we saw in Example 4-5, except, instead of calling HasOptional, you are now calling HasRequired. This lets Code First know that you want this one-to-many relationship to be required rather than optional. Run the application again and you will see that the database looks the same as it did in Figure 4-1 when you used Data Annotations to configure the relationship to be required.

Note

If you are configuring a one-to-one relationship where both ends are required or both ends are optional, Code First will need some more information from you to work out which end is the principal and which end is the dependent. This area of the Fluent API can get very confusing! The good news is that you probably won’t need to use it very often. This topic is covered in detail in Working with One-to-One Relationships.

Working with Foreign Keys

So far we’ve just looked at relationships where there isn’t a foreign key property in your class. For example, Lodging just contains a reference property that points to Destination, but there is no property to store the key value of the Destination it points to. In these cases, we have seen that Code First will introduce a foreign key in the database for you. But now let’s look at what happens when we include the foreign key property in the class itself.

In the previous section you added some configuration to make the Lodging to Destination relationship required. Go ahead and remove this configuration so that we can observe the Code First conventions in action. With the configuration removed, add a DestinationId property into the Lodging class:

public int DestinationId { get; set; }

Once you have added the foreign key property to the Lodging class, go ahead and run your application. The database will get recreated in response to the change you just made. If you inspect the columns of the Lodgings table, you will notice that Code First has automatically detected that DestinationId is a foreign key for the Lodging to Destination relationship and is no longer generating the Destination_DestinationId foreign key (Figure 4-3).

Lodgings with FK after DestinationId is added to the class
Figure 4-3. Lodgings with FK after DestinationId is added to the class

As you might expect by now, Code First has a set or rules it applies to try and locate a foreign key property when it discovers a relationship. The rules are based on the name of the property. The foreign key property will be discovered by convention if it is named [Target Type Key Name], [Target Type Name] + [Target Type Key Name], or [Navigation Property Name] + [Target Type Key Name]. The DestinationId property you added matched the first of these three rules. Name matching is case-insensitive, so you could have named the property DestinationID, DeStInAtIoNiD, or any other variation of casing. If no foreign key is detected, and none is configured, Code First falls back to automatically introducing one in the database.

There’s something else interesting that happens when you add the foreign key property. Without the DestinationId foreign key property, Code First convention allowed Lodging.Destination to be optional, meaning you could add a Lodging without a Destination. If you check back to Figure 2-1 in Chapter 2, you’ll see that the Destination_DestinationId field in the Lodgings table is nullable. Now with the addition of the DestinationId property, the database field is no longer nullable and you’ll find that you can no longer save a Lodging that has neither the Destination nor DestinationId property populated. This is because DestinationId is of type int, which is a value type and cannot be assigned null. If DestinationId was of type Nullable<int>, the relationship would remain optional. By convention, Code First is using the nullability of the foreign key property in your class to determine if the relationship is required or optional.

Specifying Unconventionally Named Foreign Keys

What happens when you have a foreign key, but it doesn’t follow Code First convention?

Let’s introduce a new InternetSpecial class that allows us to keep track of special pricing for the various lodgings (Example 4-7). This class has both a navigation property (Accommodation) and a foreign key property (AccommodationId) for the same relationship.

Example 4-7. The new InternetSpecial class
using System;
namespace Model
{
  public class InternetSpecial
  {
    public int InternetSpecialId { get; set; }
    public int Nights { get; set; }
    public decimal CostUSD { get; set; }
    public DateTime FromDate { get; set; }
    public DateTime ToDate { get; set; }

    public int AccommodationId { get; set; }
    public Lodging Accommodation { get; set; }
  }
}

Lodging will need a new property to contain each lodging’s special prices:

public List<InternetSpecial> InternetSpecials { get; set; }

Code First can see that Lodging has many InternetSpecials and that InternetSpecials has a Lodging (called Accommodation). Even though there’s no DbSet<InternetSpecial>, InternetSpecial is reachable from Lodging and will therefore be included in the model.

Note

You’ll learn more about how the model builder finds or ignores entities in Chapter 5.

When you run your application again, it will create the table shown in Figure 4-4. Not only is there an AccommodationId column, which is not a foreign key, but there is also another column there which is a foreign key, Accommodation_LodgingId.

InternetSpecials appears to have two foreign keys
Figure 4-4. InternetSpecials appears to have two foreign keys

You’ve seen Code First introduce a foreign key in the database before. As early as Chapter 2, you witnessed the Destination_DestinationId field added to the Lodgings table because Code First detected a need for a foreign key. It’s done the same here. Thanks to the Accommodation navigation property, Code First detected a relationship to Lodging and created the Accommodation_LodgingId field using its conventional pattern. Code First convention was not able to infer that AccommodationId is meant to be the foreign key. It simply found no properties that matched any of the three patterns that Code First convention uses to detect foreign key properties, and therefore created its own foreign key.

Fixing foreign key with Data Annotations

You can configure foreign key properties using the ForeignKey annotation to clarify your intention to Code First. Adding ForeignKey to the AccommodationId, along with information telling it which navigation property represents the relationship it is a foreign key for, will fix the problem:

[ForeignKey("Accommodation")]
public int AccommodationId { get; set; }
public Lodging Accommodation { get; set; }

Alternatively, you can apply the ForeignKey annotation to the navigation property and tell it which property is the foreign key for the relationship:

public int AccommodationId { get; set; }
[ForeignKey("AccommodationId")]
public Lodging Accommodation { get; set; }

Which one you use is a matter of personal preference. Either way, you’ll end up with the correct foreign key in the database: AccommodationId, as is shown in Figure 4-5.

AccommodationId correctly identified as the foreign key
Figure 4-5. AccommodationId correctly identified as the foreign key

Fixing foreign key with the Fluent API

The Fluent API doesn’t provide a simple way to configure the property as a foreign key. You’ll use the relationship API to configure the correct foreign key. And you can’t simply configure that piece of the relationship; you’ll need to first specify which relationship you want to configure (as you learned how to do earlier in this chapter) and then apply the fix.

To specify the relationship, begin with the InternetSpecial entity. We’ll do that directly from the modelBuilder, although you can certainly create an EntityTypeConfiguration class for InternetSpecial.

In this case, we’ll be identifying the relationship but not changing the multiplicity that Code First selected by convention. Example 4-8 specifies the existing relationship.

Example 4-8. Identifying the relationship to be configured
modelBuilder.Entity<InternetSpecial>()
  .HasRequired(s => s.Accommodation)
  .WithMany(l => l.InternetSpecials)

What we want to change, however, is something about the foreign key that is also involved with this relationship. Code First expects the foreign key property to be named LodgingId or one of the other conventional names. So we need to tell it which property truly is the foreign key—AccommodationId. Example 4-9 shows adding the HasForeignKey method to the relationship you specified in Example 4-8.

Example 4-9. Specifying a foreign key property when it has an unconventional name
modelBuilder.Entity<InternetSpecial>()
  .HasRequired(s => s.Accommodation)
  .WithMany(l => l.InternetSpecials)
  .HasForeignKey(s => s.AccommodationId);

This, too, will result in the database schema shown in Figure 4-5.

Working with Inverse Navigation Properties

So far Code First has always been able to work out that the two navigation properties we have defined on each end of a relationship are in fact different ends of the same relationship. It has been able to do this because there has only ever been one possible match. For example, Lodging only contains a single property that refers to Destination (Lodging.Destination); likewise, Destination only contains a single property that references Lodging (Destination.Lodgings).

While it isn’t terribly common, you may run into a scenario where there are multiple relationships between entities. In these cases, Code First won’t be able to work out which navigation properties match up. You will need to provide some additional configuration.

For example, what if you kept track of two contacts for each lodging? That would require a PrimaryContact and SecondaryContact property in the Lodging class. Go ahead and add these properties to the Lodging class:

public Person PrimaryContact { get; set; }
public Person SecondaryContact { get; set; }

Let’s also introduce the navigation properties on the other end of the relationship. This will allow you to navigate from a Person to the Lodging instances that they are primary and secondary contact for. Add the following two properties to the Person class:

public List<Lodging> PrimaryContactFor { get; set; }
public List<Lodging> SecondaryContactFor { get; set; }

Code First conventions will make the wrong assumptions about these new relationships you have just added. Because there are two sets of navigation properties, Code First is unable to work out how they match up. When Code First can’t be sure which navigation properties are the inverse of each other, it will create a separate relationship for each property. Figure 4-6 shows that Code First is creating four relationships based on the four navigation properties you just added.

Too many foreign keys in the Lodgings table
Figure 4-6. Too many foreign keys in the Lodgings table

Code First convention can identify bidirectional relationships, but not when there are multiple bidirectional relationships between two entities. The reason that there are extra foreign keys in Figure 4-6 is that Code First was unable to determine which of the two properties in Lodging that return a Person link up to the List<Lodging> properties in the Person class.

You can add configuration (using Data Annotations or the Fluent API) to present this information to the model builder. With Data Annotations, you’ll use an annotation called InverseProperty. With the Fluent API, you’ll use a combination of the Has/With methods to specify the correct ends of these relationships.

You can place the annotations on either end of the relationship (or both ends if you want). We’ll stick them on the navigation properties in the Lodging class (Example 4-10). The InverseProperty Data Annotation needs the name of the corresponding navigation property in the related class as its parameter.

Example 4-10. Configuring multiple bidirectional relationships from Lodging to Person
[InverseProperty("PrimaryContactFor")]
public Person PrimaryContact { get; set; }
[InverseProperty("SecondaryContactFor")]
public Person SecondaryContact { get; set; }

With the Fluent API, you need to use the Has/With pattern that you learned about earlier to identify the ends of each relationship. The first configuration in Example 4-11 describes the relationship with Lodging.PrimaryContact on one end and Person.PrimaryContactFor on the other. The second configuration is for the relationship between SecondaryContact and SecondaryContactFor.

Example 4-11. Configuring multiple relationships fluently
modelBuilder.Entity<Lodging>()
  .HasOptional(l => l.PrimaryContact)
  .WithMany(p => p.PrimaryContactFor);

modelBuilder.Entity< Lodging >()
  .HasOptional(l => l.SecondaryContact)
  .WithMany(p => p.SecondaryContactFor);

Working with Cascade Delete

Cascade delete allows dependent data to be automatically deleted when the principal record is deleted. If you delete a Destination, for example, the related Lodgings will also be deleted automatically. Entity Framework supports cascade delete behavior for in-memory data as well as in the database. As discussed in Chapter 19 of the second edition of Programming Entity Framework, it is recommended that you implement cascade delete on entities in the model if their mapped database objects also have cascade delete defined.

By convention, Code First switches on cascade delete for required relationships. When a cascade delete is defined, Code First will also configure a cascade delete in the database that it creates. Earlier in this chapter we looked at making the Lodging to Destination relationship required. In other words, a Lodging cannot exist without a Destination. Therefore, if a Destination is deleted, any related Lodgings (that are in memory and being change-tracked by the context) will also be deleted. When SaveChanges is called, the database will delete any related rows that remain in the Lodgings table, using its cascade delete behavior.

Looking at the database, you can see that Code First carried through the cascade delete and set up a constraint on the relationship in the database. Notice the Delete Rule in Figure 4-7 is set to Cascade.

Example 4-12 shows a new method called DeleteDestinationInMemoryAndDbCascade, which we’ll use to demonstrate the in-memory and database cascade delete.

The code uses a context to insert a new Destination with a couple of Lodgings. It then saves these Lodgings to the database and records the primary of the new Destination. In a separate context, the code then retrieves the Destination and its related Lodgings, and then uses the Remove method to mark the Destination instance as Deleted. We use Console.WriteLine to inspect the state of one of the related Lodging instances that are in memory. We’ll do this using the Entry method of DbContext. The Entry method gives us access to the information that EF has about the state of a given object. Next, the call to SaveChanges persists the deletions to the database.

Cascade delete defined in a database constraint
Figure 4-7. Cascade delete defined in a database constraint
Example 4-12. A method to explore cascade deletes
private static void DeleteDestinationInMemoryAndDbCascade()
{
  int destinationId;
  using (var context = new BreakAwayContext())
  {
    var destination = new Destination
    {
      Name = "Sample Destination",
      Lodgings = new List<Lodging>
      {
        new Lodging { Name = "Lodging One" },
        new Lodging { Name = "Lodging Two" }
      }
    };

    context.Destinations.Add(destination);
    context.SaveChanges();
    destinationId = destination.DestinationId;
  }

  using (var context = new BreakAwayContext())
  {
    var destination = context.Destinations
      .Include("Lodgings")
      .Single(d => d.DestinationId == destinationId);

    var aLodging = destination.Lodgings.FirstOrDefault();
    context.Destinations.Remove(destination);

    Console.WriteLine("State of one Lodging: {0}",
      context.Entry(aLodging).State.ToString());

    context.SaveChanges();
  }
}

After calling Remove on the Destination, the state of a Lodging is displayed in the console window. It is Deleted also even though we did not explicitly remove any of the Lodgings. That’s because Entity Framework used client-side cascade deleting to delete the dependent Lodgings when the code explicitly deleted (Removed) the destination.

Next, when SaveChanges is called, Entity Framework sent three DELETE commands to the database, as shown in Figure 4-8. The first two are to delete the related Lodging instances that were in memory and the third to delete the Destination.

Delete commands in response to deleting a Destination and its related Lodgings
Figure 4-8. Delete commands in response to deleting a Destination and its related Lodgings

Now let’s change the method. We’ll remove the eager loading (Include) that pulled the Lodging data into memory along with Destination. We’ll also remove all of the related code that mentions the Lodgings. Since there are no Lodgings in memory, there will be no client-side cascade delete, but the database should clean up any orphaned Lodgings because of the cascade delete defined in the database (Figure 4-7). The revised method is listed in Example 4-13.

Example 4-13. Modified DeleteDestinationInMemoryAndDbCascade code
private static void DeleteDestinationInMemoryAndDbCascade()
{
  int destinationId;
  using (var context = new BreakAwayContext())
  {
    var destination = new Destination
    {
      Name = "Sample Destination",
      Lodgings = new List<Lodging>
      {
        new Lodging { Name = "Lodging One" },
        new Lodging { Name = "Lodging Two" }
      }
    };

    context.Destinations.Add(destination);
    context.SaveChanges();
    destinationId = destination.DestinationId;
  }

  using (var context = new BreakAwayContext())
  {
    var destination = context.Destinations
      .Single(d => d.DestinationId == destinationId);

    context.Destinations.Remove(destination);
    context.SaveChanges();
  }

  using (var context = new BreakAwayContext())
  {
    var lodgings = context.Lodgings
      .Where(l => l.DestinationId == destinationId).ToList();

    Console.WriteLine("Lodgings: {0}", lodgings.Count);
  }
}

When run, the only command sent to the database is one to delete the destination. The database cascade delete will delete the related lodgings in response. When querying for the Lodgings at the end, since the database deleted the lodgings, the query will return no results and the lodgings variable will be an empty list.

Turning On or Off Client-Side Cascade Delete with Fluent Configurations

You might be working with an existing database that does not use cascade delete or you may have a policy of being explicit about data removal and not letting it happen automatically in the database. If the relationship from Lodging to Destination is optional, this is not a problem, since by convention, Code First won’t use cascade delete with an optional relationship. But you may want a required relationship in your classes without leveraging cascade delete.

You may want to get an error if the user of your application tries to delete a Destination and hasn’t explicitly deleted or reassigned the Lodging instances assigned to it.

For the scenarios where you want a required relationship but no cascade delete, you can explicitly override the convention and configure cascade delete behavior with the Fluent API. This is not supported with Data Annotations.

Keep in mind that if you set the model up this way, your application code will be responsible for deleting or reassigning dependent data when necessary.

The Fluent API method to use is called WillCascadeOnDelete and takes a Boolean as a parameter. This configuration is applied to a relationship, which means that you first need to specify the relationship using a Has/With pairing and then call WillCascadeOnDelete.

Working within the LodgingConfiguration class, the relationship is defined as:

HasRequired(l=>l.Destination)
 .WithMany(d=>d.Lodgings)

From there, you’ll find three possible configurations to add. WillCascadeOnDelete is one of them, as you can see in Figure 4-9.

WillCascadeOnDelete—one of the configurations you can add to a fluently described relationship
Figure 4-9. WillCascadeOnDelete—one of the configurations you can add to a fluently described relationship

Now you can set WillCascadeOnDelete to false for this relationship:

HasRequired(l=>l.Destination)
  .WithMany(d=>d.Lodgings)
  .WillCascadeOnDelete(false)

Warning

If you add the above code to your project, remove it again before continuing with the rest of this chapter.

This will also mean that the database schema that Code First generates will not include the cascade delete. The Delete Rule that was Cascade in Figure 4-7 would become No Action.

In the scenario where the relationship is required, you’ll need to be aware of logic that will create a conflict, for example, the current required relationship between Lodging and Destination that requires that a Lodging instance have a Destination or a DestinationId. If you have a Lodging that is being change-tracked and you delete its related Destination, this will cause Lodging.Destination to become null. When SaveChanges is called, Entity Framework will attempt to synchronize Lodging.DestinationId, setting it to null. But that’s not possible and an exception will be thrown with the following detailed message:

The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

The overall message here is that you have control over the cascade delete setting, but you will be responsible for avoiding or resolving possible validation conflicts caused by not having a cascade delete present.

Setting Cascade Delete Off in Scenarios That Are Not Supported by the Database

Some databases (including SQL Server) don’t support multiple relationships that specify cascade delete pointing to the same table. Because Code First configures required relationships to have cascade delete, this results in an error if you have two required relationships to the same entity. You can use WillCascadeOnDelete(false) to turn off the cascade delete setting on one or more of the relationships. Example 4-14 shows an example of the exception message from SQL Server if you don’t configure this correctly.

Example 4-14. Exception message when Code First attempts to create cascade delete where multiple relationships exist
System.InvalidOperationException was unhandled
  Message=The database creation succeeded, but the creation of the database objects
           did not.
  See InnerException for details.

 InnerException: System.Data.SqlClient.SqlException
   Message=Introducing FOREIGN KEY constraint 'Lodging_SecondaryContact' on table
           'Lodgings' may cause cycles or multiple cascade paths. Specify ON DELETE
            NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY
            constraints. Could not create constraint. See previous errors.

Exploring Many-to-Many Relationships

Entity Framework supports many-to-many relationships. Let’s see how Code First responds to a many-to-many relationship between two classes when generating a database.

If you’ve had many-to-many relationships when using the database-first strategy, you may be familiar with the fact that Entity Framework can create many-to-many mappings when the database join table contains only the primary keys of the related entities. This mapping rule is the same for Code First.

Let’s add a new Activity class to the model. Activity, shown in Example 4-15, will be related to the Trip class. A Trip can have a number of Activities scheduled and an Activity can be scheduled for a variety of trips. Therefore Trip and Activity will have a many-to-many relationship.

Example 4-15. A new class, Activity
using System.ComponentModel.DataAnnotations; 
using System.Collections.Generic;

namespace Model
{
  public class Activity
  {
    public int ActivityId { get; set; }
    [Required,MaxLength(50)]
    public string Name { get; set; }

    public List<Trip> Trips { get; set; }
  }
}

There’s a List<Trip> in the Activity class. Let’s also add a List<Activity> to the Trip class for the other end of the many-to-many relationship:

public List<Activity> Activities { get; set; }

When you run the application again, Code First will recreate the database because of the model changes. Code First convention will recognize the many-to-many relationship and build a join table in the database with the appropriate keys of the tables it’s joining. The keys are both primary keys of the join table and foreign keys pointing to the joined tables, as shown in Figure 4-10.

The ActivityTrips join table created by Code First for the many-to-many relationship
Figure 4-10. The ActivityTrips join table created by Code First for the many-to-many relationship

Notice that Code First convention created the table name by combining the names of the classes it’s joining and then pluralizing the result. It also used the same pattern we’ve seen earlier for creating the foreign key names. In Chapter 5, which focuses on table and column mappings, you’ll learn how to specify the table name and column names of the join table with configurations.

Once the many-to-many relationship exists, it behaves in just the same way that many-to-many relationships have worked in Entity Framework since the first version. You can query, add, and remove related objects by using the class properties. In the background, Entity Framework will use its knowledge of how your classes map to the database to create select, insert, update, and delete commands that incorporate the join table.

For example, the following query looks for a single trip and eager loads the related Activities:

var tripWithActivities = context.Trips
  .Include("Activities").FirstOrDefault();

The query is written against the classes with no need to be concerned about how the trip and its activities are joined in the database. Entity Framework uses its knowledge of the mappings to work out the SQL that performs the join and returns a graph that includes all of the activities that are bound to the first trip. This may not be exactly how you would construct the SQL, but remember that Entity Framework constructs the store SQL based on a pattern that can be used generically regardless of the structure of your classes or the schema of the database.

The result is a graph of the trip and its activities. Figure 4-11 shows the Trip in a debug window. You can see it has two Activities that were pulled back from the database along with the Trip.

Trip and Activities graph that is a result of querying across a many-to-many relationship
Figure 4-11. Trip and Activities graph that is a result of querying across a many-to-many relationship

Expanding the Activities in Figure 4-12 shows the details of the activities returned. Notice that there is a circular reference pointing back to the Trip that each Activity is attached to in memory.

Inspecting the Activities returned along with the Trip
Figure 4-12. Inspecting the Activities returned along with the Trip

Entity Framework took care of the joins to get across the join table without you having to be aware of its presence. In the same way, any time you do inserts, updates, or deletes within this many-to-many relationship, Entity Framework will work out the proper SQL for the join without you having to worry about it in your code.

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; }
}

Working with One-to-One Relationships

There is one type of relationship that Code First will always require configuration for: one-to-one relationships. When you define a one-to-one relationship in your model, you use a reference navigation property in each class. If you have a reference and a collection, Code First can infer that the class with the reference is the dependent and should have the foreign key. If you have two collections, Code First knows it’s many-to-many and the foreign keys go in a separate join table. However, when Code First just sees two references, it can’t work out which class should have the foreign key.

Let’s add a new PersonPhoto class to contain a photo and a caption for the people in the Person class. Since the photo will be for a specific person, we’ll use PersonId as the key property. And since that is not a conventional key property, it needs to be configured as such with the Key Data Annotation (Example 4-20).

Example 4-20. The PersonPhoto class
using System.ComponentModel.DataAnnotations;
 namespace Model
{
  public class PersonPhoto
  {
    [Key]
    public int PersonId { get; set; }
    public byte[] Photo { get; set; }
    public string Caption { get; set; }

    public Person PhotoOf { get; set; }
  }
}

Let’s also add a Photo property to the Person class, so that we can navigate both directions:

public PersonPhoto Photo { get; set; }

Remember that Code First can’t determine which class is the dependent in these situations. When it attempts to build the model, an exception is thrown, telling you that it needs more information:

Unable to determine the principal end of an association between the types ‘Model.PersonPhoto’ and ‘Model.Person’. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.

This problem is most easily solved by using a ForeignKey annotation on the dependent class to identify that it contains the foreign key. When configuring one-to-one relationships, Entity Framework requires that the primary key of the dependent also be the foreign key. In our case PersonPhoto is the dependent and its key, PersonPhoto.PersonId, should also be the foreign key. Go ahead and add in the ForeignKey annotation to the PersonPhoto.PersonId property, as shown in Example 4-21. Remember to specify the navigation property for the relationship when adding the ForeignKey annotation.

Example 4-21. Adding the ForeignKey annotation
public class PersonPhoto
{
  [Key]
  [ForeignKey("PhotoOf")]
  public int PersonId { get; set; }
  public byte[] Photo { get; set; }
  public string Caption { get; set; }

  public Person PhotoOf { get; set; }
}

Running the application again will successfully create the new database table, although you’ll see that Entity Framework didn’t deal well with pluralizing the word “Photo.” We’ll clean that up in Chapter 5, when you learn how to specify table names. More importantly, notice that PersonId is now both a PK and an FK. And if you look at the PersonPhoto_PhotoOf foreign key constraint details, you can see that it shows the People.PersonId is the primary table/column in the relationship and PersonPhotoes.PersonId is the foreign key table/column (Figure 4-14). This matches our intent.

PersonPhotoes with foreign key
Figure 4-14. PersonPhotoes with foreign key

Earlier in this chapter, we also saw that you could place the ForeignKey annotation on the navigation property and specify the name of the foreign key property (in our case, that is PersonId). Since both classes contain a PersonId property, Code First still won’t be able to work out which class contains the foreign key. So you can’t employ the configuration in that way for this scenario.

Of course, there is also a way to configure this in the Fluent API. Let’s assume for the moment that the relationship is one-to-zero-or-one, meaning a PersonPhoto must have a Person but a Person isn’t required to have a PersonPhoto. We can use the HasRequired and WithOptional combination to specify this:

modelBuilder.Entity<PersonPhoto>()
  .HasRequired(p => p.PhotoOf)
  .WithOptional(p => p.Photo);

That’s actually enough for Code First to work out that PersonPhoto is the dependent. Based on the multiplicity we specified, it only makes sense for Person to be the principal and PersonPhoto to be the dependent, since a Person can exist without a PersonPhoto but a PersonPhoto must have a Person.

Notice that you didn’t need to use HasForeignKey to specify that PersonPhoto.PersonId is the foreign key. This is because of Entity Framework’s requirement that the primary key of the dependent be used as the foreign key. Since there is no choice, Code First will just infer this for you. In fact, the Fluent API won’t let you use HasForeignKey. In IntelliSense, the method simply isn’t available after combining HasRequired and WithOptional.

Configuring One-to-One Relationships When Both Ends Are Required

Now let’s tell Code First that a Person must have a PersonPhoto (i.e., it’s required). With Data Annotations, you can use the same Required data annotation that we used earlier on Destination.Name and Lodging.Name. You can use Required on any type of property, not just primitive types:

[Required]
public PersonPhoto Photo { get; set; }

Now update the Main method to call the InsertPerson method you defined back in Chapter 3 and run the application again. An exception will be thrown when SaveChanges is called. In the exception, Entity Framework’s Validation API reports that the validation for the required PersonPhoto failed.

Ensuring that the sample code honors the required Photo

If you want to leave the Photo property as Required and avoid the validation errors, you can modify the InsertPerson and UpdatePerson methods so that they add data into the Photo field. For the sake of keeping the code simple, we’ll just stuff a single byte into the Photo’s byte array rather than worrying about supplying an actual photo.

In the InsertPerson method, modify the line of code that instantiates a new Person object to add the Photo property, as shown in Example 4-22.

Example 4-22. Modifying the InsertPerson method to add a Photo to the new Person
var person = new Person
{
  FirstName = "Rowan",
  LastName = "Miller",
  SocialSecurityNumber = 12345678,
  Photo = new PersonPhoto { Photo = new Byte[] { 0 } }
};

In the UpdatePerson method, we’ll add some code to ensure that any Person data you’ve already added before we created the Photo class gets a Photo at the same time that you update. Modify the UpdatePerson method as shown in Example 4-23 so that it allocates a new PersonPhoto when it tries to update a person without a photo.

Example 4-23. Modification to UpdatePerson to ensure existing Person data has a Photo
private static void UpdatePerson()
{
  using (var context = new BreakAwayContext())
  {
    var person = context.People.Include("Photo").FirstOrDefault();
    person.FirstName = "Rowena";
    if (person.Photo == null)
    {
      person.Photo = new PersonPhoto { Photo = new Byte[] { 0 } };
    }

    context.SaveChanges();
  }
}

The updated method will use Include to also retrieve the Person’s Photo when fetching the data from the database. We then check if the Person has a Photo and add a new one if they do not. Now the Photo requirement in the Person class will be fulfilled any time you execute the InsertPerson and UpdatePerson methods.

Configuring one-to-one with the Fluent API

Not surprisingly, you can also configure the same relationship with the Fluent API. But you’ll need to let Code First know which class is the principal and which is the dependent. If both ends are required, this can’t simply be implied from the multiplicity.

You might expect to call HasRequired followed by WithRequired. However, if you start with HasRequired, you will have the additional options of WithRequiredPrincipal and WithRequiredDependent in the place of WithRequired. These methods take into account the entity that you are configuring; that is, the entity that you selected in modelBuilder.Entity or the entity that your EntityTypeConfiguration class is for. Selecting WithRequiredPrincipal will make the entity that you are configuring the principal, meaning it contains the primary key of the relationship. Selecting WithRequiredDependent will make the entity that you are configuring the dependent, meaning it will have the foreign key of the relationship.

Assuming you are configuring PersonPhoto, which you want to be the dependent, you would use the following configuration:

modelBuilder.Entity<PersonPhoto>()
  .HasRequired(p => p.PhotoOf)
  .WithRequiredDependent(p => p.Photo);

Configuring a one-to-one relationship where both ends are optional works exactly the same, except you start with HasOptional and select either WithOptionalPrincipal or WithOptionalDependent.

Summary

In this chapter, you’ve seen that Code First has a lot of intelligence about relationships. Code First conventions are able to discover relationships of any multiplicity with or without a provided foreign key. But there are many scenarios where your intentions don’t coincide with Code First conventions. You’ve learned many ways to “fix” the model by configuring with Data Annotations and the Fluent API. You should have a good understanding of how to work with relationships in the Fluent API based on its Has/With pattern.

In the next chapter, we’ll look at another set of mappings in Code First that are all about how your classes map to the database, including how to map a variety of inheritance hierarchies.

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