Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework: Auto-updating foreign key when setting a new object reference

I am porting an existing application from Linq to SQL to Entity Framework 4 (default code generation).

One difference I noticed between the two is that a foreign key property is not updated when resetting the object reference. Now I need to decide how to deal with this.

For example supposing you have two entity types, Company and Employee. One Company has many Employees.

In Linq To SQL, setting the company also sets the company id:

var company=new Company(ID=1);
var employee=new Employee();
Debug.Assert(employee.CompanyID==0);
employee.Company=company;
Debug.Assert(employee.CompanyID==1); //Works fine!

In Entity Framework (and without using any code template customization) this does not work:

var company=new Company(ID=1);
var employee=new Employee();
Debug.Assert(employee.CompanyID==0);
employee.Company=company;
Debug.Assert(employee.CompanyID==1); //Throws, since CompanyID was not updated!

How can I make EF behave the same way as LinqToSQL? I had a look at the default code generation T4 template, but I could not figure out how to make the necessary changes. It seems like a one-liner should do the trick, but I could not figure out how to get the ID property for a given reference.

like image 906
Adrian Grigore Avatar asked Jun 11 '10 15:06

Adrian Grigore


2 Answers

From what I can see in the default T4 template, the foreign key properties of entities are not directly linked to the entity reference associated with the key.

Theres a couples to approach to your issue regarding migration from Linq to SQL to EF4. One of them would be to register to the AssociationChanged event of your associations so that it updates your field automatically. In your context, one approach could be something like like this :

// Extends Employee entity
public partial class Employee
{
    private void CompanyChanged(Object sender, CollectionChangeEventArgs e)
    {
        // Apply reactive changes; aka set CompanyID
        // here
    }

    // Create a default constructor that registers your event handler
    public Employee()
    {
        this.CompanyReference.AssociationChanged += CompanyChanged;
    }
}

Personally, if you want to limit the maintenance required to maintain this sort of logic, I'd suggest changing your T4 template (either change it yourself or find one) so that it sets the CompanyId when Company is changed as shown previously.

Gil Fink wrote a pretty good introdution to T4 templates with EF4, and you can look up Scott Hanselman wrapped a good bunch of useful links and ressources to work with T4 templates.

On a last note, unless I'm mistaken, accessing foreign keys directly as propeties of an entity is something new from EF3.5 to 4. In 3.5, only way you could access it was through the associated entity (Employee.Company.CompanyID). I believe the feature was added in EF4 so that you didn't have to load associations (using "include") in order to get the foreign key when selecting from the data store.

Perhaps EF's take on this would be, if you got the association, go through the association to get the ID, first and foremost. But that's just speculation as I got no quotes to back it up.

[EDIT 2010-06-16]: After a quick readthrough and analysis of the edmx xml elements, I found one called ReferentialConstraint which appears to contain foreign key fields to a specfic FK_Relation.

Heres the code snippet to modify inside a default T4 edmx template, section Write Navigation Properties. (Template_RegionNavigationProperties), around line 388 of an unmodified template. Try to ignore the horrible formatting...

<#=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
        {
            // edit begins here
            if(value != null)
            {
                // Automatically sets the foreign key attributes according to linked entity

<#
            AssociationType association = GetSourceSchemaTypes<AssociationType>().FirstOrDefault(_ => _.FullName == navProperty.RelationshipType.FullName);
            foreach(var cons in  association.ReferentialConstraints)
            {
                foreach(var metadataProperty in cons.FromProperties)
                {
#>
                this.<#=metadataProperty.Name#> = value.<#=metadataProperty.Name#>;
                //this._<#=metadataProperty.Name#> = value._<#=metadataProperty.Name#>; // use private field to bypass the OnChanged events, property validation and the likes..

<#
                }
            }
#>  
            }
            else
            {
                // what usually happens in Linq-to-SQL when an association is set to null
                // here
            }
            // edit ends here

            ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<<#=MultiSchemaEscape(navProperty.ToEndMember.GetEntityType(), code)#>>("<#=navProperty.RelationshipType.FullName#>", "<#=navProperty.ToEndMember.Name#>").Value = value;
        }
    }

I roughly tested it, but it's a given that theres some validation and such missing. Perhaps it could give you a tip towards a solution regardless.

like image 59
Dynami Le Savard Avatar answered Oct 21 '22 16:10

Dynami Le Savard


Thanks for this solution. I've enhanced it (does not depend on specific naming conventions anymore) and encluded in a fix that also fixes an other issue with the Entity Framework template.

Check here for my solution and fixed code generation template

like image 28
codetuner Avatar answered Oct 21 '22 15:10

codetuner