Understanding Entity Framework Associations

by Rudi17. February 2011 22:21

Introduction

Since Entity Framework 4, EF supports two different types of associations. Microsoft names these association types independent associations and foreign key associations, but I like to call them mapped associations and constrained associations.

You will also find more information about these association types in the MSDN article “Defining and Managing Relationships”:
http://msdn.microsoft.com/en-us/library/ee373856.aspx

Mapped associations (independent associations)

A mapped association is one that is mapped on the storage model, on tables and columns. Such an association is created when you create or update your entity model from database and choose not to create foreign key properties. A mapped association can also be added manually to the model, for instance by using the association tool from the EDM toolbox.

The context menu of a mapped association, containing a menu item to access the table mapping of the association.
The context menu of a mapped association, containing a menu item to access the table mapping of the association.

 By right-clicking on a mapped association and selecting ‘Table Mapping’ from the context menu, you can define the table and columns to map the association on.

Constrained associations (foreign key associations)

When choosing to have foreign key properties created during the creation or update of the entity model from database, Visual Studio.NET Entity Data Model Wizard will create another type of associations: constrained associations.

In contrary to mapped associations, constrained associations are not mapped directly on tables and columns, but on scalar properties (which on their turn are mapped on columns). The context menu of a constrained association does not show a ‘Table Mapping’ option.

The context menu of a constrained association.
The context menu of a constrained association.

You can also manually create (or edit) a constrained association by using the association tool from the EDM toolbox, and then edit the ‘Constraint’ property from the properties box of the association. The constraint of an association is similar to an association table mapping, except that is a mapping in terms of the conceptual model (in terms of scalar properties instead of in terms of columns).

When you delete the constraint of a constrained association, it automatically becomes a mapped association (that is, the ‘Table Mapping’ menu item appears in the context menu of the association), and by (clearing the table mapping and) adding a constraint on a mapped association, it becomes a constrained association.

Understanding the difference

So why do two association types exist in Entity Framework ? The reason is: to accommodate the rule that a database column may only be mapped to a single ‘item’ (scalar property or association) in Entity Framework. When adding support to foreign key properties in Entity Framework 4, this introduced a situation where a single column (the foreign key column) would be mapped on both the foreign key property (which is basically a regular scalar property) and the association. Therefore, the EF designers decided to add constrained properties which would not be mapped on columns but rather be mapped on (or constrained by) the foreign key properties.

Within the EDMX file, the difference is clear. A mapped association is defined as both an association in the conceptual model:

<Association Name="OrderCustomer"> 
  <End Type="OrderingModel.Customer" Role="Customer" Multiplicity="0..1" /> 
  <End Type="OrderingModel.Order" Role="Order" Multiplicity="*" /> 
</Association>

and a mapping between the conceptual model’s association and storage model’s tables and columns in the ‘C-S mapping’ section of the EDMX file:

<AssociationSetMapping Name="OrderCustomer" 
 TypeName="OrderingModel.OrderCustomer" StoreEntitySet="Order"> 
  <EndProperty Name="Order"> 
    <ScalarProperty Name="Id" ColumnName="Id" /> 
  </EndProperty> 
  <EndProperty Name="Customer"> 
    <ScalarProperty Name="Id" ColumnName="CustomerId" /> 
  </EndProperty> 
  <Condition ColumnName="CustomerId" IsNull="false" /> 
</AssociationSetMapping>

While a constrained association is defined entirely in the conceptual model (as its constraints are in terms of the same conceptual model):

<Association Name="OrderCustomer"> 
  <End Role="Customer" Type="OrderingModel.Customer" Multiplicity="0..1" /> 
  <End Role="Order" Type="OrderingModel.Order" Multiplicity="*" /> 
  <ReferentialConstraint> 
    <Principal Role="Customer"> 
      <PropertyRef Name="Id" /> 
    </Principal> 
    <Dependent Role="Order"> 
      <PropertyRef Name="CustomerId" /> 
    </Dependent> 
  </ReferentialConstraint> 
</Association>

Many-to-many associations

Foreign key properties are only supported on the 1 or 0..1 side of an association. Therefore, many-to-many associations, which have only many sides, don’t support the use of foreign key properties. Hence, many-to-many associations are always defined as mapped associations.

In this article I will further concentrate on one-to-many associations.

Choosing the association type

Choosing the type of association is easy because the choice depends on whether you want to have foreign key properties or not. If you want to have a foreign key property, then the matching association must be a constrained one (as it is not allowed to map a column to both a foreign key property and an association).

If you do not (want to) have a foreign key property, then the association would be a mapped one (as there is no foreign key property to use in the constraint definition).

So the question is, do you want to have foreign key properties ?

Foreign key properties can help increasing performance of your application by limiting the complexity and number of database queries. For instance, if you need the id of an order together with the id of its customer (or need to use them in a where clause), with foreign keys you can request properties of the same Order entitytype: order.Id and order.CustomerId. While if you did not define foreign key properties, you would need to traverse the association connecting both which means you’ll have to perform an extra query or extra join on database level: order.Id and order.Customer.Id.

But then again, it is perfectly legal to mix mapped and constrained associations together in one entity model and to switch an association from one type to another (by adding or removing constraints and defining or clearing the table mapping) anytime you like. So why bother, really ?

Known issues

Both types of associations are supposed to show the same behavior (support lazy loading, etc.). However, so far I’m aware of 2 issues related to associations in Entity Framework. (Both issues happen with and can be solved through the default generation template. I did not investigate those issues on the Self-Tracking entities template or the POCO template.)

The first issue has been described by:

http://stackoverflow.com/questions/3023993/entity-framework-auto-updating-foreign-key-when-setting-a-new-object-reference

This issue tells us that changes made on foreign key properties are not always reflected on the association properties. As this issue relates to the use of foreign key properties, it only occurs with constrained associations.

I have inspired my solution from the one described in the StackOverflow post, but corrected it as it was not correct under all circumstances.

The other issue tells us that a reference property is not considered changed if it is set to null without first reading its value. As a result the following code does not work as expected (provided an order with Id==1 exists, it already has a customer associated to it, and the CustomerId foreign key column is nullable), while uncommenting the commented line solves the issue:

// Perform update: 
using (var context = new OrderEntities()) 
{ 
    Order order = context.Orders.First(x => x.Id == 1); 
    // dummy = order.Customer; // solves the issue 
    order.Customer = null; 
    context.SaveChanges(); 
} 

Solving the issues

For a real solution of those issues we must update the code generation template of Entity framework.

The first step is to add the code generation template to our project by choosing “Add Code Generation Item…” in the context menu of the Entity Model diagram, and then choose for the “ADO.NET EntityObject Generator”. Using this template you still get ‘regular’ entities (no POCO’s or self-tracking entities).

The Add Code Generation Item menu option

This option adds a “model.tt” file to our project which is a text template used to generate the entity context and classes.

Next, we’ll ‘fix’ that template. From line 381 to (including) 391 you’ll find the following code:

<#=code.SpaceAfter(NewModifier(navProperty))#><#=Accessibility.ForProperty(navProperty)#> <#=MultiSchemaEscape(navProperty.ToEndMember.GetEntityType(), code)#> <#=code.Escape(navProperty)#> 
{ 
    <#=code.SpaceAfter(Accessibility.ForGetter(navProperty))#>get 
    { 
        return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<<#=MultiSchemaEscape(navProperty.ToEndMember.GetEntityType(), code)#>>("<#=navProperty.RelationshipType.FullName#>", "<#=navProperty.ToEndMember.Name#>").Value; 
    } 
    <#=code.SpaceAfter(Accessibility.ForSetter(navProperty))#>set 
    { 
        ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<<#=MultiSchemaEscape(navProperty.ToEndMember.GetEntityType(), code)#>>("<#=navProperty.RelationshipType.FullName#>", "<#=navProperty.ToEndMember.Name#>").Value = value; 
    } 
} 

Replace this code with the following:

<#=code.SpaceAfter(NewModifier(navProperty))#><#=Accessibility.ForProperty(navProperty)#> <#=MultiSchemaEscape(navProperty.ToEndMember.GetEntityType(), code)#> <#=code.Escape(navProperty)#> 
{ 
    <#=code.SpaceAfter(Accessibility.ForGetter(navProperty))#>get 
    { 
        return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<<#=MultiSchemaEscape(navProperty.ToEndMember.GetEntityType(), code)#>>("<#=navProperty.RelationshipType.FullName#>", "<#=navProperty.ToEndMember.Name#>").Value; 
    } 
    <#=code.SpaceAfter(Accessibility.ForSetter(navProperty))#>set 
    { 
<#      AssociationType association = GetSourceSchemaTypes<AssociationType>().FirstOrDefault(_ => _.FullName == navProperty.RelationshipType.FullName); 
        if (association.ReferentialConstraints.Count > 0) { 
#> 
        #region Fix: Foreign key properties not correctly updated between new objects 
        // Update foreign key properties: 
        if(value != null) 
        { 
<#          foreach(var cons in  association.ReferentialConstraints) 
            { 
                for(int i=0; i<cons.FromProperties.Count; i++) 
                { 
                    var fromProperty = cons.FromProperties[i]; 
                    var toProperty = cons.ToProperties[i]; 
#> 
            this.<#=toProperty.Name#> = value.<#=fromProperty.Name#>; 
<# 
                } 
            } 
#> 
        } 
        else 
        { 
<#          foreach(var cons in  association.ReferentialConstraints) 
            { 
                for(int i=0; i<cons.FromProperties.Count; i++) 
                { 
                    var fromProperty = cons.FromProperties[i]; 
                    var toProperty = cons.ToProperties[i]; 
                    if (toProperty.Nullable) { 
#> 
            this.<#=toProperty.Name#> = null; 
<# 
                    } else { 
#> 
            throw new ArgumentNullException("value", "<#= String.Format("{0} of {1} cannot be null.", navProperty.Name, entity.Name) #>"); 
<# 
                    } 
                } 
            } 
#> 
        } 
        #endregion 
 
<# 
        } 
#> 
        #region Fix: Reference properties set to null require a read-first to persist 
        // When assigned value=null, preload the property before updating it 
        // to ensure EF4 detects the referential property change: 
        if(value == null) 
        { 
            var dummy = this.<#=code.Escape(navProperty)#>; 
        } 
        #endregion 
 
        // Update the relationship (=original implementation): 
        ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<<#=MultiSchemaEscape(navProperty.ToEndMember.GetEntityType(), code)#>>("<#=navProperty.RelationshipType.FullName#>", "<#=navProperty.ToEndMember.Name#>").Value = value; 
 
        // Call partial On~Changed method: 
        this.On<#=code.Escape(navProperty)#>Changed(); 
    } 
} 
 
partial void On<#=code.Escape(navProperty)#>Changed();

Or download the “Model_fixed.tt” file (see download links below).

With this new template, a property that previously generated to:

public Customer Customer
{
    get
    {
        return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Customer>("Ef4UpdateBugDbModel.OrderCustomer", "Customer").Value;
    }
    set
    {
        ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Customer>("Ef4UpdateBugDbModel.OrderCustomer", "Customer").Value = value;
    }
} 

Will now generate into:

public Customer Customer
{
    get
    {
        return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Customer>("OrderEntityModel.OrderCustomer", "Customer").Value;
    }
    set
    {
        #region Fix: Foreign key properties not correctly updated between new objects
        // Update foreign key properties:
        if(value != null)
        {
            this.CustomerId = value.Id;
        }
        else
        {
            this.CustomerId = null;
        }
        #endregion
    
        #region Fix: Reference properties set to null require a read-first to persist
        // When assigned value=null, preload the property before updating it
        // to ensure EF4 detects the referential property change:
        if(value == null)
        {
            var dummy = this.Customer;
        }
        #endregion
    
        // Update the relationship (=original implementation):
        ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Customer>("OrderEntityModel.OrderCustomer", "Customer").Value = value;
    
        // Call partial On~Changed method:
        this.OnCustomerChanged();
    }
}
    
partial void OnCustomerChanged();

Notice the two code regions followed by the regular implementation. And finally, an additional partial method free for you to use.

The sample

The solution you can download contains two projects containing both the same entity model, but one using constrained associations and the other using mapped associations. Both projects consist of unittests that test and demonstrate the issues. By default, the default code generation templates of Entity Framework are used.

You can switch to the ‘fixed’ templates by doing the following in each project:

1. Expand the “Model.tt” node in the Solution Explorer
2. Select the “Model.cs” file and set it’s Build Action property to “None”
3. Expand the “Model_Fixed.tt” node in the Solution Explorer
4. Select the “Model_Fixed.cs” file and set it’s Build Action property to “Compile”

Switch from default to fixed templates in the sample project.

Downloads

Sample Application
UnderstandingAssociationsSample.zip (75.50 kb)

Fixed Model.tt template
Model_Fixed.tt (47.16 kb)

Tags:

Entity Framework

Pingbacks and trackbacks (1)+

Comments are closed

About me

Widget Month List not found.

The file '/blog/widgets/Month List/widget.ascx' does not exist.X

Widget Page List not found.

The file '/blog/widgets/Page List/widget.ascx' does not exist.X