Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Entity Framework : Include avec des jointures, inclure réellement les données

[new:31/03/2013]Entity Framework est d’une grande puissance mais c’est parfois une machine délicate. Le cas de Inlcude avec une jointure en est un exemple vivant, l’Include semble ne pas fonctionner… Mais il y a une solution…

Eager Loading

Dans de nombreux cas il s’avère nécessaire de charger les entités liées à l’entité principale qui est requêtée, c’est le “eager loading” opposé au “lazy loading” qui diffère le chargement des grappes de données pour économiser bande passante et temps de traitement.

Voici par exemple une requête simple :

var results = 
        from d in context.Dossiers.Include(“Client”)
        where d.Produit==”produit”
        select d;

Ici on imagine une entité “Dossier” et un ensemble de données “Dossiers”. Chaque Dossier contient une référence vers le client concerné. S’agissant d’un lien vers une autre entité (de type Client) les informations se trouvant dans le dossier sélectionné se limitent à l’ID du client par défaut.

Pour “remonter” la fiche client associée il faut avoir marqué les métadonnées de Dossier avec l’attribut d’inclusion, mais il faut aussi ajouter “Include(“Client”)” à la requête. La première action informe E.F. que l’inclusion est possible, la seconde la réclame effectivement.

Quand nous exécutons la requête ci-dessus nous obtenons bien une liste de Dossiers dont la propriété de navigation “Client” pointe réellement sur une fiche client utilisable.

Comme disent les américains, so far, so good…

Essayons maintenant de joindre la table des Dossiers à son historique dans la même requête :

var results = 
        from d in context.Dossiers.Include(“Client”)
        join h in context.Historiques on d.IdDossier equals h.IdDossier
        where d.Produit==”produit” && h.DateDerniereAction>xxxxxxx
        select d;

 

Peu importe le sens de cette requête fictive donnée pour l’exemple, si vous la transposez à un cas réel chez vous, vous aurez la mauvaise surprise de voir, ou plutôt de ne plus rien voir de la fiche Client des dossiers retournés !

Pourtant l’Include est toujours là.

Quel est le problème ?

La jointure (qui pourrait aussi être exprimée par un simple second “from” et un “where”, le problème serait le même) change la forme de la requête, même temporairement…

Dans le premier exemple, ce sont des Dossiers qui sont sélectionnés, toutes les colonnes sont dans la requête, y compris celles concernées par l’Include. Du début à la fin du traitement de la requête seules des entités de type Dossier sont traitées, de fait les colonnes restent les mêmes. L’Include fonctionne.

Dans le second exemple quand l’Include est indiqué la requête inclut toutes les colonnes de Dossiers, mais la jointure (avec join ou un second from) modifie la forme de la requête, temporairement certes, mais cette modification impose de changer les colonnes puisqu’à ce moment la requête doit intégrer les colonnes des deux tables. Ce changement dans les colonnes traitées par la requête passe par un traitement qui '”oublie” l’Include de départ qui ne porte que sur une des deux tables. Include ne marche plus…

Solutions

Selon la requête, une solution consiste à réécrire une partie des conditions en utilisant des tests de type “any” ou autre groupage de données sur une sous requête qui n’est pas liée à la table qui possède des Includes. Je n’entrerai pas dans les détails de ces réécritures il en existe des dizaines de variantes selon les cas.

De plus cette solution est lourde car certaines requêtes ne seront pas évidentes à réécrire d’une autre façon.

L’astuce que je vous propose est bien plus simple car elle ne change rien à la requête :

var results = 
        (from d in context.Dossiers // plus d’include
        join h in context.Historiques on d.IdDossier equals h.IdDossier
        where d.Produit==”produit” && h.DateDerniereAction>xxxxxxx
        select d).Include(“Client”);

 

Parfois la solution est si simple qu’on n’y pense pas… Entourez votre requête de parenthèses et ajoutez l’include à la fin. Et vous rétablirez le fonctionnement de ce dernier !

Attention toutefois : cela ne fonctionne que si la requête retourne une entité et non pas un “new { }” par exemple. Dans un tel cas il faudra traiter les données en deux temps, remonter les dossiers avec leur client associés puis créer une nouvelle liste en mémoire ne contenant que les informations désirées. Cela peut être très couteux en espace mémoire et il faudra éventuellement ajouter une entité à votre schéma (de type POCO) pour ne retourner que les données nécessaires. Cela est surtout important lorsqu’on utilise Entity Framework avec les RIA Services ou d’autres services WCF.

Conclusion

Entity Framework est une machine sophistiquée, faire des requêtes simples avec Linq se démontre en quelques secondes dans une démo. S’en servir sur des cas réels révèle bien entendu des limitations ou des comportements qui ne sont plus aussi triviaux que dans une démo…

Heureusement la bête est assez souple et puissante pour trouver des contournements, encore faut-il connaitre ces derniers !

Stay Tuned !

blog comments powered by Disqus