Dot.Blog

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

LINQ Join sur plusieurs champs

[new:02/02/2011]Linq et ses merveilles... Linq et ses jointures... Parlons-en des jointures : savez-vous comment faire une jointure sur plusieurs champs à la fois ?

Join on / Equals

Pour faire une jointure avec Linq, c’est facile, il suffit d’utiliser JOIN ON, un peu comme en SQL.

Prenons un petit exemple simple, l’exemple 103 des fameux “101 exemples” (il y en a un petit plus que 101 mais l’histoire de ces exemples a été débattu il y a longtemps – voir mon billet 101 exemples ou vous trouverez l’application exemple que j’avais écrite et qui est bien utile pour se rappeler des différentes syntaxes !). Le voici l’exemple 103 :

public void Linq103()
{
    string[] categories = new string[]{ 
        "Beverages", 
        "Condiments", 
        "Vegetables", 
        "Dairy Products", 
        "Seafood" };
 
    List<Product> products = Data.GetProductList();
 
    var q =
        from c in categories
        join p in products on c equals p.Category into ps
        select new { Category = c, Products = ps };
 
    foreach (var v in q)
    {
        Txt+=string.Format(v.Category + ":");
        foreach (var p in v.Products)
        {
            Txt+=string.Format("   " + p.ProductName);
        }
    }
}

Ce qui est intéressant c’est bien entendu la requête (var q=...).

Comme on le constate le JOIN s’écrit un peu comme le FROM avec une clause ON en plus. Cette dernière indique la paire de champs à considérer pour créer la jointure.

On notera ici le stockage intermédiaire dans “ps” ce qui n’est qu’une possibilité, pas une obligation (regardez mon application exemple des 101 exemples et vous trouverez les nombreuses variantes de JOIN, et du reste de la syntaxe Linq).

Première chose : il faut utiliser “equals” et non le signe “==”.

Seconde chose : c’est une paire de champs, un de chaque entity set (en gros un de chaque “table”).

Mais avec deux champs ?

C’est facile se dit-on, avec deux champs je rajoute “&&” suivi de la seconde paire. Quelque chose du style :

join p in products on c.Field1 equals p.FieldA && c.Field2 equals p.FieldB

Perdu !

Hélas, ça ne passe pas. Le JOIN ne marche que sur UNE paire de champs.

L’astuce

Il y a en a une, vous le savez déjà, sinon je n’écrirais pas ce billet... Et elle passe par la création d’une classe anonyme, du moins d’instances de classe anonyme.

L’idée est de conserver une paire de valeur qui peuvent être comparées par un Equals. Toute la ruse se situe dans le fait qu’au lieu d’un champ simple nous allons créer une instance anonyme comportant l’ensemble des champs à tester. Ainsi l’exemple (faux) donné juste ci-dessus s’écrira en réalité :

join p in products on 
   new { c.Field1, c.Field2 }
    equals
   new { p.FieldA, p.FieldB }

Futé non ?

Un petit détail qui n’est pas évident ici : comme on créée une instance de classe anonyme, on peut, comme ci-dessus, mettre directement les champs les uns derrière les autres avec une virgule. Cela va créer automatiquement un type anonyme dont les propriétés porteront les mêmes noms.

Or, dans l’exemple que je viens de donner, on trouve des “c.Field1” et “c.Field2” d’un côté et de l’autre des “p.FieldA” et “p.FieldB”. Cela ne marchera pas... Car les deux instances anonymes ne seront pas compatibles entre elles puisque la première contiendra deux propriétés nommées “Field1” et “Field2” alors que le second type aura des propriétés s’appelant “FieldA” et “FieldB”.

Très souvent les JOIN s’expriment sur des champs (plus exactement pour Linq, des Propriétés) qui portent le même nom. En tout cas cela devrait être le cas... Mais la réalité étant un peu différente de l’idéal, il arrive tout aussi souvent que les noms de propriétés dans les deux classes à comparer ne soient pas rigoureusement identiques.

C’est foutu alors ?

Mais non. Il suffit d’adopter une syntaxe plus verbeuse pour l’instance anonyme en indiquant clairement le nom de ses propriétés. Ainsi l’exemple devient syntaxiquement correct en l’écrivant ainsi :

join p in products on
    new { ChampA=c.Field1, ChampB=c.Field2 }
    equals
    new { ChampA=p.FieldA, ChampB=p.FieldB }

Et voilà... En indiquant explicitement les noms des propriétés des instances anonymes on s’assure que le compilateur traitera bien les deux “new” comme faisant appel à la même classe de base, anonyme certes, mais la même.

Conclusion

Rien de compliqué dans ce billet, une astuce tout simple mais qui est loin d’être connue de tous... Et qui mérite d’être notée quelque part.

Ne vous fatiguez pas à sortir un stylo et un papier que vous allez perdre de toute façon, vous retrouverez facilement cette astuce et bien d’autres juste en vous souvenant d’une seule chose : avant de chercher ailleurs il suffit de venir en premier sur Dot.Blog Smile !

Stay Tuned !

blog comments powered by Disqus