Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Atteint de JavaScriptite Aiguë [Cyril Durand]

Expert ASP.net Ajax et WCF, Cyril Durand parle dans son blog de point techniques sur ASP.net, ASP.net Ajax, JavaScript, WCF et .net en général. Cyril est également consultant indépendant, n'hésitez pas à le contacter pour de l'assistance sur vos projets

Actualités

  • Blog de Cyril DURAND, passionné de JavaScript, Ajax, ASP.net et tout ce qui touche au developpement Web Client-Side.

    N'hésitez pas à me contacter pour vos projets .net : architecture, accompagnement, formation, ...

    View Cyril Durand's profile on LinkedIn
    hit counters


    Expertise Commerce server et BizTalk

Include typé et Entity Framework

Par défaut, lorsque l’on fait une requête Entity Framework, les objets connexes ne sont pas chargés, il faut explicitement indiquer que l’on souhaite charger ces objets.

Il existe 2 solutions pour charger les objets connexes. Soit on active le lazy-loading, ainsi les objets seront chargés lors de l’accès à la propriété. Soit on indique à Entity Framework quelles sont les objets connexes à charger, cela se fait via la méthode Include de ObjectQuery. Cette méthode prend un paramètre de type String indiquant quelles sont les propriétés à charger en même temps que l’objet.

Ainsi, si l’on souhaite charger les clients en même temps qu’une commande, il faudra inclure la propriété Customer, le code ci-dessous illustre mon propos.

var q = entities.Orders.Include("Customer").Where(o => o.CustomerId == 3);

foreach(var order in q){ 
  // order.Customer ne sera ici pas null
}

Le problème de ce bout de code est que si un jour on change le nom de la propriété Customer et que l’on oublie de changer le code du Include, il n’y aura aucune erreur de compilation, le code plantera avec une exception de type “InvalidOperationException” lors de l’exécution :

System.InvalidOperationException: A specified Include path is not valid. The EntityType 'XXX.Data.Entities.Order' does not declare a navigation property with the name 'Cuuustomer'.

Afin de palier à ce problème, il est possible de créer sa propre méthode d’extension Include qui prend non pas une chaine de caractère mais une lambda expression permettant de spécifier la propriété que l’on veut inclure. Le code ci-dessus devient alors :

var q = entities.Orders.Include(o => o.Customer).Where(o => o.CustomerId == 3);

foreach(var order in q){ 
  // order.Customer ne sera ici pas null
}

Ainsi, le nom de la propriété Customer sera vérifié lors de la compilation, ce qui limite donc grandement les erreurs.

Si l’on veut inclure plus d’une profondeur d’objet, par exemple, si l’on souhaite inclure les clients d’une commande ainsi que ses adresses, Entity Framework propose de séparer les propriétés par des points. Le bout de code suivant fonctionnera :

var q = entities.Orders.Include("Customer.Addresses").Where(o => o.CustomerId == 3);

foreach(var order in q){ 
  // order.Customer.Addresses ne sera ici pas null
}

Dans ce cas, il est assez simple de faire une méthode d’extension pour ce cas particulier, on pourrait alors écrire ceci :

var q = entities.Orders.Include(o => o.Customer.Addresses).Where(o => o.CustomerId == 3);

foreach(var order in q){ 
  // order.Customer.Addresses ne sera ici pas null
}

Le problème se pose lorsque l’on veut inclure une propriété après une collection, par exemple lorsque l’on veut inclure les pays de toutes les adresses du client d’une commande. Dans ce cas, puisqu’on travaille sur une collection d’adresses, il n’est pas possible de retomber sur un élément pour accéder à une propriété précise. Une solution courante est d’utiliser la méthode First, lors de la conversion de l’expression en String, les appels à la méthode First ne seront pas pris en compte. Exemple :

var q = entities.Orders.Include(o => o.Customer.Addresses.First().Country).Where(o => o.CustomerId == 3);

foreach(var order in q){ 
  foreach(var address in order.Customer.Addresses){
      // address.Coutnry ne sera ici pas null 
  }
}

Une autre solution possible serait d’avoir plusieurs paramètre sur notre include et que chaque paramètre soit une lambda expression qui prend un entrée soit le type de l’argument précédent, soit le type de l’élément de la collection si l’élément précédent était une collection. Ainsi, on pourrait écrire notre include de cette façon :

var q = entities.Orders.Include(o => o.Customer, c => Addresses, a => a.Country).Where(o => o.CustomerId == 3);

foreach(var order in q){ 
  foreach(var address in order.Customer.Addresses){
      // address.Coutnry ne sera ici pas null 
  }
}

Avec cette solution, il existe une multitude de signature possible. Pour générer ces différentes signatures, j’ai utilisé un fichier T4.

Mon fichier T4 permet de générer des signatures avec une profondeur de 6, cela veut dire qu’il sera possible de mettre 6 paramètres à notre fonction Include. Une profondeur de 6 nous génère 64 signatures différents, au-delà, Visual Studio a beaucoup de mal à afficher l’intellisense … Cette limitation est acceptable, avoir des includes a plus de 6 niveaux de profondeur est déjà important, le SQL généré risque d’être déjà très complexe. De plus, lorsque l’on ne travaille pas sur une collection, on peut réunir les paramètres, l’include ci-dessus peut donc s’écrire ainsi :

var q = entities.Orders.Include(o => o.Customer.Addresses, a => a.Country).Where(o => o.CustomerId == 3);

foreach(var order in q){ 
  foreach(var address in order.Customer.Addresses){
      // address.Coutnry ne sera ici pas null 
  }
}

Le code de mon fichier T4 se trouve sur csharpfr : Entity Framework : Avoir un includé typé. Pour l’utiliser, il suffit d’inclure le fichier T4 et le fichier ExpressionExtensions.cs dans votre solution.

Pour ma part, je trouve cette solution assez élégante, le seul problème est le nombre de signature assez importante que cela génère. Et vous, qu’en pensez-vous ? Préférez-vous cette solution à la solution du .First() ? Avez-vous une autre solution ? Comment faites-vous vos includes ?

Posted: mardi 17 janvier 2012 16:31 par cyril
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 :

Commentaires

Matthieu MEZIL a dit :

Salut Cyril,

EF et T4 : rien de tel pour me rendre heureux :)

J'avais fait un post pour avoir des Include typés avec EF v1(http://blogs.developpeur.org/matthieu/archive/2008/06/06/entity-framework-include-avec-func-v2.aspx).

Avec les versions récentes d'EF, c'est géré comme ceci :

var q = entities.Orders.Include(o => o.Customer.Addresses.Select(a => a.Country));

Attention tout de même, l'Include ça tue les perfs !

J'ai écris une série de post sur le sujet (sur mon blog anglais uniquement pour l'instant) :

http://msmvps.com/blogs/matthieu/archive/2011/12/18/why-does-include-is-an-anti-pattern-imho.aspx

http://msmvps.com/blogs/matthieu/archive/2011/12/19/ef-why-include-method-is-an-anti-pattern-imho-even-with-many-to-one-navigation-properties.aspx

http://msmvps.com/blogs/matthieu/archive/2011/12/21/ef-why-include-method-is-an-anti-pattern-imho-3-3.aspx

http://msmvps.com/blogs/matthieu/archive/2011/12/21/ef-why-include-method-is-an-anti-pattern-imho-conclusion.aspx

Hope that helps

Matthieu

# janvier 17, 2012 17:26

cyril a dit :

var q = entities.Orders.Include(o => o.Customer.Addresses.Select(a => a.Country));

C'est ce qui me semblait avoir lu mais j'arrive pas à retrouver ma source, t'as un lien à partager ?

Je trouve cette syntaxe vraiment bof, encore plus compliqué à expliquer, meme si au final, l'interet est très limité.

L'autre piste que je n'ai pas creusé et de passer par :

.BeginInclude(o => o.Customer.Addresses)

  .Include(a => a.Country)

.End()

Ca éviterait peut être d'avoir un nombre d'implémentation infinie mais pas sur.

# janvier 17, 2012 20:15

Matthieu MEZIL a dit :

http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx

J'aime pas trop non plus cette idée de Select dans l'Include. C'est pour ça que j'avais préféré opter pour une apporche Include(o => o.Customer.Include(c => c.Addresses.Include(a => a.Country))) que je trouve plus explicite...

# janvier 18, 2012 09:31
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- L’application des MiniDrones Parrot est aussi disponible pour Windows 8.1 par Blog de Jérémy Jeanson le 10-28-2014, 15:01

- L’application des MiniDrones Parrot est enfin disponible pour Windows Phone par Blog de Jérémy Jeanson le 10-27-2014, 09:49

- Mise à jour Samsung 840 EVO sur core server par Blog de Jérémy Jeanson le 10-27-2014, 05:59

- MVP Award 2014 ;) par Blog de Jérémy Jeanson le 10-27-2014, 05:42

- « Naviguer vers le haut » dans une librairie SharePoint par Blog de Jérémy Jeanson le 10-07-2014, 13:21

- PowerShell: Comment mixer NAGIOS et PowerShell pour le monitoring applicatif par Blog Technique de Romelard Fabrice le 10-07-2014, 11:43

- ReBUILD 2014 : les présentations par Le blog de Patrick [MVP Office 365] le 10-06-2014, 09:15

- II6 Management Compatibility présente dans Windows Server Technical Preview avec IIS8 par Blog de Jérémy Jeanson le 10-05-2014, 17:37

- Soft Restart sur Windows Server Technical Preview par Blog de Jérémy Jeanson le 10-03-2014, 19:43

- Non, le certificat public du CA n’est pas un certificat client !!! par Blog de Jérémy Jeanson le 10-03-2014, 00:08