Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Abonnements

Comment récupérer les FK avec EF v1 ?

Avant de répondre à cette question, je vais d’abord répondre à une autre : quand est-ce qu’on a besoin des clés étrangères (FK) ?

  • Pour changer la relation d’une propriété sans charger l’entité relative. Cependant, on peut déjà faire ceci avec l’EntityReference.
  • Pour le binding. C’est vraiment dans ce cas que les FK manquent cruellement.

Avec la ComboBox (Windows Forms), on n’en a pas besoin car on peut se passer de la propriété ValueMember :

categoriesComboBox.DataSource = context.Categories;

categoriesComboBox.DisplayMember = "CategoryName";

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

En revanche, le problème se pose pour les DataGridViewComboBoxColumn ainsi que pour les DropDownList ASP. Dans ce cas, on a réellement besoin des FK.

Ma première idée était d’ajouter la propriété tout simplement :

partial class Product

{

    public int CategoryID

    {

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

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

    }

}

Cela marche très bien. Cependant, le problème est qu’on pourrait être tenté de l’utiliser dans une requête LINQ To Entities, ce qui engendrerait une exception.

Quand on se penche un peu sur le mécanisme du binding, on s’aperçoit que celui-ci n’utilise pas directement les propriétés mais utilise les PropertyDescriptor. Ceux-ci sont par défaut générés à partir des propriétés. Cependant, il est possible de modifier cela en implémentant l’interface 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;

    }

}

Cool, ça marche nickel !

Cependant, il est évident que cela va vite devenir pénible s’il faut faire ça pour chaque FK !

Même avec VS 2008, il est possible d’utiliser le template de génération de code T4 à partir d’un edmx. Mon idée a donc été de modifier le template par défaut pour ajouter automatiquement le code adéquat.

Vous pouvez télécharger ce template ici.

Afin de vous familiariser avec le T4 Smile, voici la partie la plus importante du template :

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());

}

Vous pouvez maintenant utiliser les FK dans vos bindings sans le moindre effort !

Elle est pas belle la vie ? Wink

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Publié mardi 21 juillet 2009 23:54 par Matthieu MEZIL

Classé sous : , , , ,

Commentaires

Pas de commentaires

Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Merci par Blog de Jérémy Jeanson le 10-01-2019, 20:47

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01