Background

If you've been reading the EF Design Blog you will have seen that we recently announced something called "FK Associations" for the EF in .NET 4.0.

However in .NET 3.5 SP1, we only support Independent Associations. What that means is that the FK Columns aren't available as Properties in the entity. Which in turn means you are forced to build relationships via References to other Entities.

For example, unlike LINQ to SQL, this is not possible in the EF:

product.CategoryID = 2;

Because there is no "CategoryID" property in product entity.

You have to do something like this instead:

product.Category = ctx.Categories.First(c => c.ID == 2);

I.e. you need to use the Category reference. Which means you have to have a Category in memory, or do a query to get one like the example above.

The problem is that not having a property for the CategoryID that you can set directly can be a big pain, and that is why we've added "FK Associations" and "FK Properties" in .NET 4.0.

All well and good, but as Julie Lerman has a habit of reminding me, what about everyone who is using .NET 3.5 SP1 today? Our new features don't really help today.

So...

How can you set the CategoryID if it doesn't actually exist?

Trick question. You can't of course.

But you can achieve the same effect, with code like this:

product.CategoryReference.EntityKey
   = new EntityKey("MyContainer.Categories", "ID", 2);

Looks a little complicated doesn't it, so let's break it down:

  1. For each reference "xxx" (like Category) the entity framework also generates an "xxxReference" property that returns an EntityReference. (EntityReference is used heavily by the EF to provide all the plumbing and services required for things like maintaining relationship consistency, and doing Entity lookups based on EntityKeys)
  2. One of the properties of the EntityReference is an EntityKey. We need to set the EntityKey to a new EntityKey if we wish to change the value of the Foreign Key.
  3. In order to create a new EntityKey we need to know 3 things.
    1. The value we want to set the "Foreign Key" to. To be consistent with the earlier code snippets that is 2.
    2. The fully qualified name of the EntitySet that the target Entity belongs to. In this case our target is a Category and we get Categories from the "MyContainer.Categories" set.
    3. The name of the Primary Key Property in the target entity. In this case the primary key of Category is the "ID" property.

Now obviously this is not the sort of code you want to write everywhere you need it. So lets hide this away. So ...

 

How can you use this code to fake a Foreign Key property?

Luckily the Entity Framework generates partial classes for Entities, so you can simply put this logic in your own Product partial class like this:

public int CategoryID {
    get {
       
if (this.CategoryReference.EntityKey == null) return 0;
       
else return (int) this.CategoryReference
            .EntityKey.EntityKeyValues[0].Value;
    }
    set {
        this.CategoryReference.EntityKey 
           = new EntityKey("MyContainer.Categories", "ID", value);
    }
}

Notice that we use the CategoryReference in the getter too. This means that our CategoryID property is simply a view over the CategoryReference, which means that if the EF makes any changes under the hood we don't need to be notified.

With this in place you can write what we wanted all along:

product.CategoryID = 2;

And that can be extremely useful in lots of scenarios, for example MVC controllers and data-binding.

Extra Credit

This solution is essentially just a workaround that hides some of the limitations of independent associations, and like all workarounds it has limitations.  The key then is to make an informed tradeoff between the benefits and the drawbacks. So here are some of the key drawbacks:

  1. Properties in the partial class are not recognized by the Entity Framework, so you can't use them in your LINQ queries.
  2. By referencing the fully qualified EntitySet name in our setter, we have coupled your Entity Class directly to the EntitySet. Now for most people this is not a problem, but it won't work if you try to re-use your entity classes between contexts or if you use MEST.
  3. There are probably more... I'll add them as I think of them!