views:

232

answers:

1

Hi,

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 are 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.

+2  A: 

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.

Dynami Le Savard
Thanks for your reply. Adding an event handler for every association would work, but would be a lot of (errorprone) work on the current model plus a great possibility that I forget to do this later. So the T4 approach really seems the way to go. As I said, I already looked at the T4 template. Generally I know how T4 works, I already have a fair bit of experience with it, but I could not figure out how to get the ID Property for a given reference from the EF metadata. I gave up after a few hours and started this post...
Adrian Grigore
Indeed, it appears theres no link between the FK Property and the FK reference, not even a custom attribute. Do you follow any kind of naming convention for your foreign keys that is constant across your relationnal DB ?
Dynami Le Savard
Usually yes, so reflection would probably work. If there is no other option (I was hoping I missed some link between the reference and the id), I'll try to use this approach instead.
Adrian Grigore
Aight, I looked into how the edmx xml elements are parsed, and I found one called ReferentialConstraint, which seems to contain something we could use. See my edit.
Dynami Le Savard
Thanks, will have a look tomorrow. Sorry for not noticing the comment earlier.
Adrian Grigore