How to have FK in EF v1?

Before answering this question, I will start with another one: why do we need FK?

  • to be able to change a relationship without loading the related entity but we can already do it with the EntityReference property.
  • for the binding

With the windows forms ComboBox, we don’t need it because you don’t have to systematically set the ValueMember property:

categoriesComboBox.DataSource = context.Categories;

categoriesComboBox.DisplayMember = "CategoryName";

categoriesComboBox.DataBindings.Add("SelectedItem", product, "Category");

But the problem is for the DataGridViewComboBoxColumn and the ASP DropDownList. In these cases, we really need the FK property.

My first idea was to add the property:

partial class Product

{

    public int CategoryID

    {

        get { return (int)CategoryReference.EntityKey.EntityKeyValues.First().Value; }

        set { CategoryReference.EntityKey = new EntityKey("NorthwindEntities.Categories", "CategoryID", value); }

    }

}

Ok, it works. However, the problem is the fact that we can been tempted to use it in a LINQ To Entities query. But, of course, in this case, it fails.

The binding doesn’t directly use the property of the class but the PropertyDescriptor which are, by default, generated from the properties. My idea is not to add a new property but to add a new PropertyDescriptor. For this, we have to implement the ICustomTypeDescriptor.

partial class Product : ICustomTypeDescriptor

{

    AttributeCollection ICustomTypeDescriptor.GetAttributes()

    {

        return TypeDescriptor.GetAttributes(this, true);

    }

 

    string ICustomTypeDescriptor.GetClassName()

    {

        return TypeDescriptor.GetClassName(this);

    }

 

    string ICustomTypeDescriptor.GetComponentName()

    {

        return TypeDescriptor.GetComponentName(this);

    }

 

    TypeConverter ICustomTypeDescriptor.GetConverter()

    {

        return TypeDescriptor.GetConverter(this);

    }

 

    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()

    {

        return TypeDescriptor.GetDefaultEvent(this);

    }

 

    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()

    {

        return TypeDescriptor.GetDefaultProperty(this);

    }

 

    object ICustomTypeDescriptor.GetEditor(Type editorBaseType)

    {

        return TypeDescriptor.GetEditor(this, editorBaseType);

    }

 

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)

    {

        return TypeDescriptor.GetEvents(attributes);

    }

 

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()

    {

        return TypeDescriptor.GetEvents(this);

    }

 

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)

    {

        var props = TypeDescriptor.GetProperties(this, attributes, true).Cast<PropertyDescriptor>().ToList();

        props.Add(new FKPropertyDescriptor<Product>(p => p.CategoryReference, "CategoryID", "CategoryCategoryID", typeof(int)));

        return new PropertyDescriptorCollection(props.ToArray());

    }

 

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()

    {

        return ((ICustomTypeDescriptor)this).GetProperties(null);

    }

 

    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)

    {

        return this;

    }

}

 

public class FKPropertyDescriptor<T> : PropertyDescriptor

{

    private Type _propertyDescriptorType;

    private Func<T, EntityReference> _getEntityReference;

 

    public string PropertyName { get; private set; }

 

    public FKPropertyDescriptor(Func<T, EntityReference> getEntityReference, string propertyName, string propertyDescriptorName, Type propertyDescriptorType)

        : base(propertyDescriptorName, new Attribute[0])

    {

        _getEntityReference = getEntityReference;

        PropertyName = propertyName;

        _propertyDescriptorType = propertyDescriptorType;

    }

 

    public override bool CanResetValue(object component)

    {

        return false;

    }

 

    public override Type ComponentType

    {

        get { return typeof(T); }

    }

 

    public override object GetValue(object component)

    {

        return _getEntityReference((T)component).EntityKey.EntityKeyValues.First(ekv => ekv.Key == PropertyName).Value;

    }

 

    public override bool IsReadOnly

    {

        get { return false; }

    }

 

    public override Type PropertyType

    {

        get { return _propertyDescriptorType; }

    }

 

    public override void ResetValue(object component)

    {

    }

 

    public override void SetValue(object component, object value)

    {

        var entityKey = _getEntityReference((T)component).EntityKey;

        _getEntityReference((T)component).EntityKey = new EntityKey(string.Concat(entityKey.EntityContainerName, ".", entityKey.EntitySetName), entityKey.EntityKeyValues.Where(ekm => ekm.Key != PropertyName).Union(new[] { new EntityKeyMember(PropertyName, value) }));

    }

 

    public override bool ShouldSerializeValue(object component)

    {

        return false;

    }

}

Great, it works!

However, it would be very boring to do it for each FK!

As Danny wrote here, it’s possible to use the T4 template with VS 2008.  So my idea is to change the default template to add it automatically.

You can download it here.

The most important part of the template is this one:

PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)

{

    var props = TypeDescriptor.GetProperties(this, attributes, true).Cast<PropertyDescriptor>().ToList();

<#        

    foreach(NavigationProperty property in entity.NavigationProperties.Where(n => n.DeclaringType == entity && n.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many))

    {

        string elementTypeName = GetEntityType(property.ToEndMember).Name;

        EntityType elementType = Edm.AllEntities.First(et => et.Name == elementTypeName);

        foreach (EdmMember keyMember in elementType.KeyMembers)

        {

            string keyMemberName = keyMember.Name;

#>

    props.Add(new FKPropertyDescriptor<<#= entity.Name #>>(p => p.<#= property.Name #>Reference, "<#= keyMemberName #>", "<#= string.Concat(property.Name, keyMemberName) #>", typeof(<#= new CSharpCodeLanguage().Format(keyMember.TypeUsage) #>)));

<#

        }

    }

#>

    return new PropertyDescriptorCollection(props.ToArray());

}

What is cool now is the fact that with zero effort, you can use the FK in your bindings.

Enjoy [:)]

This entry was posted in 10511, 7671, 7672, 7674. Bookmark the permalink.

3 Responses to How to have FK in EF v1?

  1. Lucas says:

    In FKPropertyDescriptor.SetValue(), don’t you mean to use Concat() instead of Union() ?

  2. Warren says:

    Now with new changes in Entity Framework in VS 2010, does this no longer apply?

  3. Matthieu MEZIL says:

    No, it is useless with .NET 4

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>