Associations in EF Code First: Part 3 – Shared Primary Key Associations

This is the third post in a series that explains entity association mappings with EF Code First. This series includes:
In the previous blog post I demonstrated how to map a special kind of one-to-one association—a composition with complex types. We argued that the relationship between User and Address is best represented with a complex type mapping and we saw that this is usually the simplest way to represent one-to-one relationships but comes with some limitations.

In today’s blog post I’m going to discuss how we can address those limitations by changing our mapping strategy. This is particularly useful for scenarios that we want a dedicated table for Address, so that we can map both User and Address as entities. One benefit of this model is the possibility for shared references— another entity class (let’s say Shipment) can also have a reference to a particular Address instance. If a User has a reference to this instance, as her BillingAddress, the Address instance has to support shared references and needs its own identity. In this case, User and Address classes have a true one-to-one association.

Introducing the Revised Model

In this revised version, each User could have one BillingAddress (Billing Association). Also a Shipment always needs a destination address for delivery (Delivery Association). The following shows the class diagram for this domain model (note the multiplicities on association lines):
In this model we assumed that the billing address of the user is the same as her delivery address. Now let’s create the association mappings for this domain model. There are several choices, the first being a One-to-One Primary Key Association.

Shared Primary Key Associations

Also know as One-to-One Primary Key Associations, means two related tables share the same primary key values. The primary key of one table is also a foreign key of the other. Let’s see how we can create a primary key association mapping with Code First.

How to Implement a One-to-One Primary Key Association with Code First

First, we start with the POCO classes. As you can see, we've defined BillingAddress as a navigation property on User class and another one on Shipment class named DeliveryAddress. Both associations are unidirectional since we didn't define related navigation properties on Address class as for User and Shipment.
public class User
{
    public int UserId { getset; }
    public string Name { getset; }
 
    public virtual Address BillingAddress { getset; }
}
 
public class Address
{
    public int AddressId { getset; }
    public string Street { getset; }
    public string City { getset; }
    public string ZipCode { getset; }
}
        
public class Shipment
{
    public int ShipmentId { getset; }     
    public string State { getset; }
 
    public virtual Address DeliveryAddress { getset; }
}
 
public class Context : DbContext
{
    public DbSet<User> Users { getset; }
    public DbSet<Address> Addresses { getset; }
    public DbSet<Shipment> Shipments { getset; }
}

How Code First Sees the Associations in our Object Model: One-to-Many

Code First reads the model and tries to figure out the multiplicity of the associations. Since the associations are unidirectional, Code First takes this as if one Address has many Users and Many Shipments and will create a one-to-many association for each of them. In other words, a unidirectional association is always inferred as One-to-Many by Code First. So, what we were hoping for —a one-to-one association, is not inline with the Code First conventions.

How to Change the Multiplicity of the Associations to One-to-One by Using the Conventions

Obviously, one way to turn our associations to one-to-one is by making them bidirectional. That is, adding a new navigation property to Address class of type User and another one of type Shipment. By doing that we simply signal Code First that we are looking to have one-to-one associations since for example User has an Address and also Address has a User. Therefore, Code First will change the multiplicity to one-to-one and this will solve the problem.

Should We Make the Associations Bidirectional?

As always, the decision is up to us and depends on whether we need to navigate through our objects in that direction in the application code. In this case, we’d probably conclude that the bidirectional association doesn’t make much sense. If we call anAddress.User, we are saying “give me the user who has this address”, not a very reasonable request. So this is not a good option. Instead we'll keep our object model as it is and will explicitly ask Code First to make our associations one-to-one.

How to Change the Multiplicity to One-to-One with Fluent API

The following code is all that is needed to make the associations to be one-to-one. Note how the multiplicities in the UML class diagram (e.g. 1 on User and 0..1 on address) has been translated to the fluent API code by using HasRequired and HasOptional methods:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().HasOptional(u => u.BillingAddress)
                               .WithRequired();
    
    modelBuilder.Entity<Shipment>().HasRequired(u => u.DeliveryAddress)
                                   .WithOptional();
}
Also it worth noting that when we are mapping a one-to-one association with fluent API, we don't need to specify the foreign key as we would do when mapping a one-to-many association with HasForeignKey method. Since EF only supports one-to-one associations on primary keys, it will automatically create the relationship in the database on the primary keys.

Database Schema

The mapping result for our object model is as follows (note the Identity column on Users table):

Referential Integrity

In relational database design the referential integrity rule states that each non-null value of a foreign key must match the value of some primary key. But wait, how does it even applies here? All we have is just three primary keys referencing each other! Who is the primary key and who is the foreign key? The best way to find the answer of this question is to take a look at the properties of the relationships in the database that has been created by Code First:
As you can see, Code First adds a foreign key constraint which links the primary key of the Addresses table to the primary key of the Users table and adds another foreign key constraint that links the primary key of the Shipments table to the primary key of the Addresses table. The foreign key constraint means that a user has to exist for a particular address but not the other way around. In other words, the database guarantees that an Addresses row’s primary key references a valid Users primary key and a Shipments row’s primary key references a valid Addresses primary key.

How Code First Determines the Principal and Dependent Ends in an Association?

Code First has rules to determine the principal and dependent ends of an association. For one-to-many relationships the many end is always the dependent, but it gets a little tricky in one-to-one associations. In one-to-one associations Code First decides based on our object model, and possible data annotations or fluent API code that we may have. For example in this case, we used the following fluent API code to configure the User-Address association:
modelBuilder.Entity<User>().HasOptional(u => u.BillingAddress).WithRequired();
This reads as "User entity has an optional association with one Address object but this association is required for Address entity". For Code First this is good enough to make the decision: It marked User as the principal end and Address as the dependent end in the association. Since we have the same fluent API code for the second association between Address and Shipment, it marks Address as the principal end and Shipment as the dependent end in this association as well.

This decision has some consequences. In fact, the referential integrity that we saw, is the first result of this Code First's principal/dependent decision.

Second Result of Code First's Principal/Dependent Decision: Database Identity

If you take a closer look at the above DB schema, you'll notice that only UserId has a regular identifier generator (aka Identity or Sequence) and AddressId and ShipmentId does not. This is a very important consequence of the principal/dependent decision for one-to-one associations: the dependent primary key will become non-Identity by default. This make sense because they share their primary key values and only one of them can be auto generated and we need to take care of providing valid keys for the rest.

What about Cascade Deletes?

As we saw, each Address always belongs to one User and each Shipment always delivered to one single Address. We want to make sure that when we delete a User the possible dependent rows on Address and Shipment also get deleted in the database. In fact, this is one of the Referential Integrity Refactorings which called Introduce Cascading Delete. The primary reason we would apply "Introduce Cascading Delete" is to preserve the referential integrity of our data by ensuring that related rows are appropriately deleted when a parent row is deleted. By default, Code First does not enable cascade delete when it creates a one-to-one relationship in the database. As always we can override this convention by fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().HasOptional(u => u.BillingAddress)
                               .WithRequired()
                               .WillCascadeOnDelete();
    
    modelBuilder.Entity<Shipment>().HasRequired(u => u.DeliveryAddress)
                                   .WithOptional()
                                   .WillCascadeOnDelete();
}

What the Additional Methods Like WithRequiredDependent are for?

The HasRequired method returns an object of type RequiredNavigationPropertyConfiguration which defines two special methods called WithRequiredDependent and WithRequiredPrincipal in addition to the typical WithMany and WithOptional methods that we usually use. We saw that the only reason Code First could figure out principal and dependent in our associations was because our fluent API code clearly specified one end as Required and the other as Optional. But what if both endpoints are required or both are optional in the association? For example consider a scenario that a User always has one Address and Address always has one User (required on both end). Now Code First cannot pick up the principal and dependent ends on its own and that's exactly where methods like WithRequiredDependent come into play. In other words, this scenario ultimately need to be configured by fluent API and fluent API is designed in a way that will force you to explicitly specify who is dependent and who is principal in a required-required or optional-optional association scenario.

For example, this fluent API code shows how we can configure the User-Address association where both ends are required:
modelBuilder.Entity<User>().HasRequired(u => u.BillingAddress).WithRequiredDependent();
Taking a closer look at the RequiredNavigationPropertyConfiguration type also shows the idea:
public class RequiredNavigationPropertyConfiguration<TEntityType, TTargetEntityType>
{
    public DependentNavigationPropertyConfiguration<TEntityType, TTargetEntityType> WithMany();
    public CascadableNavigationPropertyConfiguration WithOptional();
    public CascadableNavigationPropertyConfiguration WithRequiredDependent();
    public CascadableNavigationPropertyConfiguration WithRequiredPrincipal();
}
As you can see, if you want to go another Required after HasRequired method, you have to either call WithRequiredDependent or WithRequiredPrincipal since there is no WithRequired method defined on RequiredNavigationPropertyConfiguration class.

Working with the Model

Here is an example for adding a new user along with its billing address. EF is smart enough to use the newly generated UserId for the AddressId as well:
using (var context = new Context())
{
    Address billingAddress = new Address()
    {
        Street = "Main St.",
        City   = "Seattle"
    };
                
    User user = new User()
    {
        Name = "Morteza",                    
        BillingAddress = billingAddress
    };
 
    context.Users.Add(user);
    context.SaveChanges();
}
The following code is an example of adding a new Address and Shipment for an existing User (assuming that we have a User with UserId = 1 in the database):
using (var context = new Context())
{
    Address deliveryAddress = new Address()
    {
        AddressId = 1,
        Street = "Main St.",                    
    };
 
    Shipment shipment = new Shipment()
    {
        ShipmentId = 1,
        State = "Shipped",                    
        DeliveryAddress = deliveryAddress
    };
 
    context.Shipments.Add(shipment);
    context.SaveChanges();
}

Limitations of This Mapping

There are two important limitations to associations mapped as shared primary key:
  • Difficulty in Saving Related Objects
  • The main difficulty with this approach is ensuring that associated instances are assigned the same primary key value when the objects are saved. For example, when adding a new Address object, it's our responsibility to provide a unique AddressId that is also valid (a User can be found with such a value as UserId.)
  • Multiple Addresses for User is Not Possible
  • With this mapping we cannot have more than one Address for User. At the beginning of this post, when we introduce our model, we assumed that the user has the same address for billing and delivery. But what if that's not the case? What if we also want to add a Home address to User for the deliveries? In the current setup, each row in the User table has at most one corresponding row in the Address table. Two addresses would require an additional address table, and this mapping style therefore wouldn’t be adequate.

Summary

In this post we learned about one-to-one associations which shared primary key is just one way to implement it. Shared primary key associations aren’t uncommon but are relatively rare. In many schemas, a one-to-one association is represented with a foreign key field and a unique constraint. In the next posts we will revisit the same domain model and will learn about other ways to map one-to-one associations that does not have the limitations of the shared primary key association mapping.

References

25 Comments

  • Hi,

    I think you have a tiny mistake in your post.
    "assuming that we have a User with UserId=2 in the database"

    Shouldn't this be "UserId=1"? Or am I missing something?

    Manu.

  • @Manu: Yes, you are correct, it should be UserId = 1. I corrected this on the post. Thank you very much! :)

  • Great article. Waiting for your next post as i am working on a mapping where i have two address fields for a user.

  • Hi, "adding a new Address and Shipment for an existing User (assuming that we have a User with UserId = 1 in the database):..." Are you forget one line: "user.BillingAddress = billingAddress" or whether I ignore anything?

  • Thanks again for great series of posts.

  • @Johnny Fee: No, we didn’t really forget anything in there. The second code snippet has nothing to do with the previous one so there is neither a billingAddress nor a user. Like I said in the post, we know for a fact that “1” is a valid UserId in the database (let’s say we retrieved this UserId earlier from another business process) so now we can use this UserId to relate an address along with a Shipment object to it. Hope this helps.

  • Hi Manavi,
    Here is my Data Model...
    public class User{&nbsp;&nbsp;&nbsp; [Key]&nbsp;&nbsp;&nbsp; public string Email { get; set; }&nbsp;&nbsp;&nbsp; public string Name { get; set; }}
    public class Profile{&nbsp;&nbsp;&nbsp; public int ProfileID { get; set; }&nbsp;&nbsp;&nbsp; public string ProfileName { get; set; }
    &nbsp;&nbsp;&nbsp; public virtual User User { get; set; }&nbsp;&nbsp;&nbsp; public virtual Address BillingAddress { get; set; }}
    public class Address{&nbsp;&nbsp;&nbsp; public string AddressId { get; set; }&nbsp;&nbsp;&nbsp; public string Street { get; set; }&nbsp;&nbsp;&nbsp; public string City { get; set; }&nbsp;&nbsp;&nbsp; public string ZipCode { get; set; }}
    public class Context : DbContext{&nbsp;&nbsp;&nbsp; public DbSet&lt;User&gt; Users { get; set; }&nbsp;&nbsp;&nbsp; public DbSet&lt;Address&gt; Addresses { get; set; }
    &nbsp;&nbsp;&nbsp; protected override void OnModelCreating(DbModelBuilder modelBuilder)&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; modelBuilder.Entity&lt;Profile&gt;().HasRequired(p =&gt; p.User).WithOptional();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; modelBuilder.Entity&lt;Profile&gt;().HasOptional(p =&gt; p.BillingAddress).WithRequired();&nbsp;&nbsp;&nbsp; }}
    If Profile ID is string, then it creates one - one mapping. But if ProfileID is int, EF is creating another column User_Email as foreign key (expected). But this time, it's Many to One between user and profile. Why do you think that is happenning?
    Can't we have a new foreign key and yet have One-One?

  • Hi Morteza,
    The entity model that you've described only works if there is no other column in Junction Table.
    In my case, I need to caputure EventId too.
    Users attend Events. During the event, User A can express interest in multiple users. User B can express interest in multiple users. If there is a match ( ie., if a user A like user B and user B like User A) then their contact info will be exchanged.
    Here it is a self referencing Many - Many relationship but I also need to know the EventID of the event they attended. That way, I can determine where they met.
    Here is my model but it is not working.
    public class User{&nbsp;&nbsp;&nbsp; public User()&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UserLike = new UserLike();&nbsp;&nbsp;&nbsp; }
    &nbsp;&nbsp;&nbsp; public int UserId { get; set; }&nbsp;&nbsp;&nbsp; public string Email { get; set; }&nbsp;&nbsp;&nbsp; public virtual UserLike UserLike { get; set; }}
    public class UserLike{&nbsp;&nbsp;&nbsp; public UserLike()&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LikesUsers = new List&lt;User&gt;();&nbsp;&nbsp;&nbsp; }
    &nbsp;&nbsp;&nbsp; public int EventId { get; set; }&nbsp;&nbsp;&nbsp; public virtual Event Event { get; set; }
    &nbsp;&nbsp;&nbsp; [Key, ForeignKey("User")]&nbsp;&nbsp;&nbsp; public int UserLikeId { get; set; }&nbsp;&nbsp;&nbsp; public virtual User User { get; set; }&nbsp;&nbsp;&nbsp; public virtual ICollection&lt;User&gt; LikesUsers { get; set; }}
    Event is a simple class. What am I doing wrong?

  • @Preetham Reddy: As I explained in&nbsp;the sixth part&nbsp;of this series, a many-to-many association cannot have a payload (e.g EventId), and if that’s the case then we have to break it down to two one-to-many associations to an intervening class and I can see you’ve correctly created this class (UserLike) to represent the extra information attached to your self-referencing many-to-many association but&nbsp;the associations from this intermediate class are not correct as we need to define exactly 2 many-to-one association from UserLike to User&nbsp;like I showed in&nbsp;the following object model:
    public class User{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public int UserId { get; set; }&nbsp;&nbsp;&nbsp; public string Email { get; set; }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    &nbsp;&nbsp;&nbsp; public virtual ICollection&lt;UserLike&gt; ThisUserLikes { get; set; }&nbsp;&nbsp;&nbsp; public virtual ICollection&lt;UserLike&gt; UsersLikeThisUser { get; set; }}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    public class UserLike{&nbsp;&nbsp;&nbsp; public int UserLikeId { get; set; }&nbsp;&nbsp;&nbsp; public int LikerId { get; set; }&nbsp;&nbsp;&nbsp; public int LikeeId { get; set; }&nbsp;&nbsp;&nbsp; public int EventId { get; set; }
    &nbsp;&nbsp;&nbsp; public User Liker { get; set; }&nbsp;&nbsp;&nbsp; public User Likee { get; set; }&nbsp;&nbsp;&nbsp; public virtual Event Event { get; set; }}
    public class Event{&nbsp;&nbsp;&nbsp; public int EventId { get; set; }&nbsp;&nbsp;&nbsp; public string Name { get; set; }}&nbsp;&nbsp;
    public class Context : DbContext {&nbsp;&nbsp;&nbsp; public DbSet&lt;User&gt; Users { get; set; } &nbsp;&nbsp;&nbsp; public DbSet&lt;Event&gt; Events { get; set; } &nbsp;&nbsp;&nbsp; protected override void OnModelCreating(DbModelBuilder modelBuilder)&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; modelBuilder.Entity&lt;User&gt;()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .HasMany(u =&gt; u.ThisUserLikes)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WithRequired(ul =&gt; ul.Liker)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .HasForeignKey(ul =&gt; ul.LikerId);
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; modelBuilder.Entity&lt;User&gt;()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .HasMany(u =&gt; u.UsersLikeThisUser)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WithRequired(ul =&gt; ul.Likee)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .HasForeignKey(ul =&gt; ul.LikeeId)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WillCascadeOnDelete(false);&nbsp;&nbsp;&nbsp; }}

  • Awesome... I've got my model working now...

  • @Preetham Reddy: One last thing before you go, you can use the following LINQ query to retrieve all the users who like each other:
    using (var context = new Context()){&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; var friends = (from u1 in context.Users&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; from likers in u1.UsersLikeThisUser&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; from u2 in u1.ThisUserLikes &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; where u2.LikeeId == likers.LikerId&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select new&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OurUser = u1.UserId,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HerFriend = u2.LikeeId &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; })&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .ToList();}

  • Hi,

    What about the case where a User can have a list of Addresses but an Address can only belong to a single User? As with the above [original] example, an Address can be associated to a Shipment. I was thinking of modelling this through a association table (e.g. UserAddress which has UserId and AddressId; ShipmentAddress which has ShipmentId and AddressId). Would very much appreciate any guidance on how to model this in EF.

    Thanks,

    Tony

  • @Tony: You don’t really need to create a join table like UserAddress unless you require a many-to-many association between your entities. The scenario you described can be mapped with a simple one-to-many association between User and Address as I showed in the following object model:
    public class User{&nbsp;&nbsp;&nbsp; public int UserId { get; set; }&nbsp;&nbsp;&nbsp; public string Name { get; set; }
    &nbsp;&nbsp;&nbsp; public virtual ICollection&lt;Address&gt; Addresses { get; set; }}
    public class Address{&nbsp;&nbsp;&nbsp; public int AddressId { get; set; }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public int UserId { get; set; }
    &nbsp;&nbsp;&nbsp; public User User { get; set; }}
    public class Context : DbContext{&nbsp;&nbsp;&nbsp; public DbSet&lt;User&gt; Users { get; set; }&nbsp;&nbsp;&nbsp; public DbSet&lt;Address&gt; Addresses { get; set; }}Note that we didn’t even need fluent API code for this as everything will be picked up by convention. You can configure the same for the association between Address and Shipment entities&nbsp;if a 1:* relationship is required between them. Please take a look at the last part of this series where I explained many-valued associations in detail. Hope this helps.

  • Thanks Mortezem.
    Sorry I wasn't clear. &nbsp;I'll give my exact example instead...
    I have the following classes...
    public class Contact {&nbsp;&nbsp; public int Id&nbsp; { get; set; }&nbsp;&nbsp; &lt;some other Contact details here&gt;}
    public class BusinessUnit {&nbsp;&nbsp; public int Id { get; set; }&nbsp;&nbsp; public List&lt;Contact&gt; Contacts&nbsp; { get; set; }&nbsp;&nbsp; &lt;some other Business Unit details here&gt;}
    public class Counterparty {&nbsp;&nbsp; public int Id { get; set; }&nbsp;&nbsp; public List&lt;Contact&gt; Contacts&nbsp; { get; set; }&nbsp;&nbsp; &lt;some other Counterparty details here&gt;}
    Business Unit and Counterparty (and possibly some other Entities in the future) can have an arbitary number of Contacts hence I am not able to put an inverse key (i.e. BusinessUnitId + BusinessUnit, CounterpartyId + Counterparty) on the Contact class. &nbsp;
    Thanks heaps.
    Tony

  • @Tony: Ok, if that’s the case then using an associative table to map a one-to-many association would make sense. In order to create this mapping, you need to set up a many-to-many association between Contact and BusinessUnit (same thing for Counterparty as well). The following fluent API code shows how:
    public class Contact {&nbsp;&nbsp; public int Id&nbsp; { get; set; }}
    public class BusinessUnit {&nbsp;&nbsp; public int Id { get; set; }&nbsp;&nbsp; public ISet&lt;Contact&gt; Contacts&nbsp; { get; set; }}
    public class Counterparty {&nbsp;&nbsp; public int Id { get; set; }&nbsp;&nbsp; public ISet&lt;Contact&gt; Contacts { get; set; }}
    public class Context : DbContext{&nbsp;&nbsp;&nbsp; public DbSet&lt;Contact&gt; Contacts { get; set; }&nbsp;&nbsp;&nbsp; public DbSet&lt;BusinessUnit&gt; BusinessUnits { get; set; }&nbsp;&nbsp;&nbsp; public DbSet&lt;Counterparty&gt; Counterparties { get; set; }
    &nbsp;&nbsp;&nbsp; protected override void OnModelCreating(DbModelBuilder modelBuilder)&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; modelBuilder.Entity&lt;BusinessUnit&gt;()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .HasMany(u =&gt; u.Contacts)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WithMany()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .Map(c =&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c.MapLeftKey("BusinessUnitId");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c.MapRightKey("ContactId");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c.ToTable("BusinessUnitContact");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; modelBuilder.Entity&lt;Counterparty&gt;()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .HasMany(u =&gt; u.Contacts)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WithMany()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .Map(c =&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c.MapLeftKey("CounterpartyId");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c.MapRightKey("ContactId");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c.ToTable("CounterpartyContact");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });&nbsp;&nbsp;&nbsp; }}After running this object model you need to enforce the relationships as a real one-to-many. For that you have to go into your database and manually create unique constraints on ContactId in both join tables (CounterpartyContact and BusinessUnitContact). In other words, you want to make sure that for example once a BusinessUnit references a Contact, this Contact won’t be referenced by any other BusinessUnit. That said, this design still has a problem: the data model allows a single Contact to be referenced by a BusinessUnit and a Counterparty at the same time. If that’s an issue then we have to come up with other ways to&nbsp;map the association between Contact and other entities, ways that ensures a Contact would be referenced by one single entity at any time.

  • @mortezam,

    ABSOLUTELY BRILLIANT!!!

    That's fixed my problem and you've saved me a lot of time with this.

    re: "The Data Model allows a single Contact to be referenced by a BusinessUnit and a Counterparty" at the same time" issue, I'll have a think about it on my end. I'm sure I'll be able to come up with a solution but would more than welcome any tips you can provide.

    Thank you so much for your help. It is very much appreciated!

    Tony

  • I 'm a little confused, i think the modelBuilder.Entity().HasRequired(u => u.BillingAddress).WithRequiredDependent() should be modelBuilder.Entity().HasRequired(u => u.BillingAddress).WithRequiredPrincipal()

    i mean here we should use WithRequiredPrincipal() instead of WithRequiredDependent()

    please help to clarify,thanks!

  • class tree
    {
    &nbsp;&nbsp;&nbsp; int id;
    &nbsp;&nbsp;&nbsp; string title;
    }
    class file
    {
    &nbsp;&nbsp;&nbsp; int id;
    &nbsp;&nbsp;&nbsp; string FileName;
    &nbsp;&nbsp;&nbsp; public virtual tree Tree;
    }

    file f = db.file.Find(id);

    f.tree.title is ALWAYS NULL ,

    why the tree instance is not loaded with the file ?

  • @yaron: First make sure that tree and every other class member on both entities is a property and not a field, since EF does not support fields yet. For example, your file entity should be look like this:



    class file
    {
    &nbsp;&nbsp;&nbsp; public int id { get; set; }
    &nbsp;&nbsp;&nbsp; public string FileName { get; set; }
    &nbsp;&nbsp;&nbsp; public virtual tree Tree { get; set; }
    }


    Now if you are reading the f.tree.title while your DbContext instance is not yet disposed then lazy loading should kick in and retrieve the tree property for you, but if you are reading the f.tree.title&nbsp;when the DbContext is already disposed then you need to eager load the Tree property when retrieving the file entity. One way to eager load a navigation property is to use the Include method:



    file f = db.file.Where(f =&gt; f.id == id).Include(f =&gt; f.Tree).Single();

  • Hello Morteza,

    I happen to be working on a similar database design. By following your way, I was able to get one step closer. However, now I’m stuck and I’m in dire need of help.

    Here’s my scenario: I have 2 tables in a 1:1 relationship, UserLogin and UserProfile.

    UserLogin consists of UserID set to autoincrement ON whereas UserProfile consists of UserID with autoincrement OFF.

    I have also overridden OnModelCreating by adding the following 2 lines:

    modelBuilder.Entity&lt;UserProfile&gt;().HasKey(u =&gt; u.UserID);

    modelBuilder.Entity&lt;UserLogin&gt;().HasOptional(u =&gt; u.UserProfile).WithRequired();

    By right, this should put them in a 1:1 relationship. However, this is the error I get:

    {"Invalid column name 'UserProfile_UserID'."}

    Am I missing something?

    In UserLogin class, I have the following:

    [Key()]
    public long UserID { get; set; }
    public virtual UserProfile UserProfile { get; set; }

    And in UserProfile class, I have this:

    public long UserID { get; set; }


  • Just noticed at the end of the article (2nd limitation) that you stated, "in the current setup, each row in the User table has a corresponding row in the Address table." That isn't true since you have a 1 -> 0...1 relationship for User -> Address, right? Just want to be clear.

    Great article by the way! Best code-first article on the internet IMO.

  • @Chase: You are correct and I changed the text a little bit so that it better reflects the model we discussed in this article. Thank you so much for your comment! :)

  • This is exactly the kind of article I was looking for. Great work.

  • what's the meaning of
    classA_ClassB_Source and ClassA_ClassB_Target
    hard to found you blog .please help

  • Great stuff. You saved me quite a bit of heart-ache. Please keep up the excellent work.

Comments have been disabled for this content.