Comment faire des jointures dans LINQ sur plusieurs champs en une seule jointure


244

J'ai besoin de faire une requête LINQ2DataSet qui fait une jointure sur plusieurs champs (comme

var result = from x in entity
join y in entity2 
       on x.field1 = y.field1 
and 
          x.field2 = y.field2

Je n'ai pas encore trouvé de solution appropriée (je peux ajouter les contraintes supplémentaires à une clause where, mais c'est loin d'être une solution appropriée, ou utiliser cette solution, mais cela suppose une équijoin).

Est-il possible dans LINQ de se joindre à plusieurs champs en une seule jointure?

ÉDITER

var result = from x in entity
             join y in entity2
             on new { x.field1, x.field2 } equals new { y.field1, y.field2 }

est la solution que j'ai référencée en supposant une équijoin ci-dessus.

De plus EDIT

Pour répondre aux critiques selon lesquelles mon exemple d'origine était une équijoin, je reconnais que, Mon exigence actuelle est pour une équijoin et j'ai déjà utilisé la solution que j'ai mentionnée ci-dessus.

J'essaie cependant de comprendre quelles possibilités et meilleures pratiques j'ai / devrais utiliser avec LINQ. Je vais devoir bientôt faire une jointure de requête de plage de dates avec un ID de table, et je devais juste anticiper ce problème.Il semble que je devrai ajouter la plage de dates dans la clause where.

Merci, comme toujours, pour toutes les suggestions et commentaires donnés


48
Juste un info pour tous ceux qui lisent ceci, si vous faites une jointure multi-champs dans des classes annon, vous DEVEZ nommer les champs dans les deux classes annon de la même manière, sinon vous obtenez des erreurs de compilation.
Mark

6
Ou plutôt, vous devez vous assurer qu'ils ont des noms correspondants. c'est-à-dire que vous pouvez simplement nommer les champs de l'un des types anon pour les faire correspondre à l'autre.
Tom Ferguson

1
Faites attention à cette réponse stackoverflow.com/a/34176502/1704458
TS

J'ai utilisé des tuples de chaque côté des égaux plutôt que des objets et cela semblait fonctionner aussi.
GHZ

Réponses:


89

La solution avec le type anonyme devrait fonctionner correctement. LINQ ne peut représenter que des équijointures (avec des clauses de jointure, de toute façon), et c'est d'ailleurs ce que vous avez dit que vous souhaitez exprimer de toute façon en fonction de votre requête d'origine.

Si vous n'aimez pas la version avec le type anonyme pour une raison spécifique, vous devez expliquer cette raison.

Si vous voulez faire autre chose que ce que vous aviez initialement demandé, veuillez donner un exemple de ce que vous voulez vraiment faire.

EDIT: Répondre à la modification dans la question: oui, pour faire une jointure "plage de dates", vous devez utiliser une clause where à la place. Ils sont vraiment sémantiquement équivalents, donc c'est juste une question d'optimisations disponibles. Les équijoins fournissent une optimisation simple (dans LINQ to Objects, qui comprend LINQ to DataSets) en créant une recherche basée sur la séquence interne - pensez-y comme une table de hachage de clé à une séquence d'entrées correspondant à cette clé.

Faire cela avec des plages de dates est un peu plus difficile. Cependant, selon exactement ce que vous entendez par "jointure de plage de dates", vous pourrez peut-être faire quelque chose de similaire - si vous prévoyez de créer des "bandes" de dates (par exemple une par an) de sorte que deux entrées qui se produisent dans le la même année (mais pas à la même date) devrait correspondre, alors vous pouvez le faire simplement en utilisant cette bande comme clé. Si c'est plus compliqué, par exemple, un côté de la jointure fournit une plage, et l'autre côté de la jointure fournit une seule date, correspondant si elle se situe dans cette plage, ce serait mieux géré avec une whereclause (après une secondefromclause) OMI. Vous pourriez faire de la magie particulièrement funky en commandant un côté ou l'autre pour trouver des correspondances plus efficacement, mais ce serait beaucoup de travail - je ne ferais ce genre de chose qu'après avoir vérifié si les performances sont un problème.


Merci, oui les performances sont ma principale préoccupation avec l'utilisation de la clause where. Je suppose qu'une clause where après la jointure effectuerait un filtre sur un plus grand ensemble de données qui aurait pu être réduit en introduisant le deuxième paramètre de jointure. J'aime l'idée de commander pour tester si je peux obtenir des gains d'efficacité
johnc

Combien d'enregistrements aurez-vous? N'oubliez pas qu'ordonner les résultats pour commencer prendra un certain temps pour commencer ...
Jon Skeet

«Ils sont vraiment sémantiquement équivalents» - avons-nous besoin du mot «vraiment» là-dedans? Peut-être que vous vouliez dire: "Ils sont vraiment équivalents sur le plan sémantique" :)
onedaywhen

136
var result = from x in entity
   join y in entity2 on new { x.field1, x.field2 } equals new { y.field1, y.field2 }

C'est ce que je cherchais car les 101 échantillons Linq n'avaient pas cela, ou du moins ce que j'ai vu.
Chris Marisic

1
@PeterX en effet c'est possible, voir ma réponse ici: stackoverflow.com/a/22176658/595157
niieani

13
Le code ci-dessus n'a pas fonctionné. Après avoir ajouté on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 } Cela a fonctionné
Ravi Ram

@Ravi Ram .. Merci .. votre commentaire a aidé
NMathur

80
var result = from x in entity1
             join y in entity2
             on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 }

Vous devez le faire si les noms de colonne sont différents dans deux entités.


6
Merci d'avoir mentionné les différents noms de colonnes. Cela a corrigé ma mauvaise expression.
Gaʀʀʏ

1
Cela a également fonctionné pour moi. Si les noms de colonne ne correspondent pas, vous obtiendrez cette erreur: «Le type de l'une des expressions dans la clause join est incorrect. L'inférence de type a échoué lors de l'appel à« GroupJoin ».
humbads

Merci d'avoir aliasé les variables clés.
Thomas.Benz

J'ai eu l'erreur que @humbads a mentionnée lorsque je n'ai pas nommé toutes les propriétés de l'int 'new {}'. Donc, juste pour info si vous en nommez un, vous devez également nommer les autres.
Ethan Melamed

MERCI BEAUCOUP
Charly H

51

Juste pour compléter cela avec une syntaxe de chaîne de méthode équivalente:

entity.Join(entity2, x => new {x.Field1, x.Field2},
                     y => new {y.Field1, y.Field2}, (x, y) => x);

Alors que le dernier argument (x, y) => xest ce que vous sélectionnez (dans le cas ci-dessus, nous sélectionnons x).


31

Je pense qu'une option plus lisible et flexible consiste à utiliser la fonction Where:

var result = from x in entity1
             from y in entity2
                 .Where(y => y.field1 == x.field1 && y.field2 == x.field2)

Cela permet également de passer facilement de la jointure interne à la jointure gauche en ajoutant .DefaultIfEmpty ().


En tant qu'utilisateur lambda de longue date maintenant (par opposition à quand j'ai posé la question), je devrais être d'accord
johnc

Serait-ce plus lent?
AlfredBr

1
Je pense qu'il devrait avoir les mêmes performances que la nouvelle { ... } equals new { ... }syntaxe. LinqPad est un excellent outil pour voir comment les expressions se comportent (script SQL si LINQ2SQL est utilisé, arborescences d'expressions, etc.)
Alexei

Pour autant que j'ai remarqué, il produit CROSS JOIN au lieu de INNER JOIN
Mariusz

@Mariusz Oui, il est logique de générer CROSS JOIN + WHERE au lieu de INNER JOIN. Pour les requêtes simples, je m'attends à ce que l'analyseur génère un très similaire.
Alexei

10
var result = from x in entity
             join y in entity2
             on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 }
             select new 
             {
               /// Columns
              };

8

vous pourriez faire quelque chose comme (ci-dessous)

var query = from p in context.T1

        join q in context.T2

        on

        new { p.Col1, p.Col2 }

        equals

         new { q.Col1, q.Col2 }

        select new {p...., q......};

Comme je l'ai mentionné en question, cela nécessite une équijoin
johnc

7

En utilisant l'opérateur de jointure, vous ne pouvez effectuer que des équijointures. D'autres types de jointures peuvent être construits à l'aide d'autres opérateurs. Je ne sais pas si la jointure exacte que vous essayez de faire serait plus facile à utiliser ces méthodes ou en changeant la clause where. La documentation sur la clause join peut être trouvée ici . MSDN a également un article sur les opérations de jointure avec plusieurs liens vers des exemples d'autres jointures.


3

Si le nom du champ est différent dans les entités

var result = from x in entity
   join y in entity2 on 
          new {
                field1=   x.field1,
               field2 =  x.field2 
             } 
          equals
         new { 
                field1= y.field1,
                field2=  y.myfield
              }
select new {x,y});

Je vous remercie. La correspondance de nom était la pièce qui me manquait.
Brett

2

En tant que chaîne de méthode complète qui ressemblerait à ceci:

lista.SelectMany(a => listb.Where(xi => b.Id == a.Id && b.Total != a.Total),
                (a, b) => new ResultItem
                {
                    Id = a.Id,
                    ATotal = a.Total,
                    BTotal = b.Total
                }).ToList();

-2
from d in db.CourseDispatches
                             join du in db.DispatchUsers on d.id equals du.dispatch_id
                             join u in db.Users on du.user_id equals u.id
                             join fr in db.Forumreports on (d.course_id + '_' + du.user_id)  equals  (fr.course_id + '_'+ fr.uid)

ça marche pour moi


C'est pour une jointure multiple, il veut faire une jointure avec plusieurs champs dans une seule jointure
theLaw

-3

Déclarez une classe (type) pour contenir les éléments que vous souhaitez joindre. Dans l'exemple ci-dessous, déclarez JoinElement

 public class **JoinElement**
{
    public int? Id { get; set; }
    public string Name { get; set; }

}

results = from course in courseQueryable.AsQueryable()
                  join agency in agencyQueryable.AsQueryable()
                   on new **JoinElement**() { Id = course.CourseAgencyId, Name = course.CourseDeveloper } 
                   equals new **JoinElement**() { Id = agency.CourseAgencyId, Name = "D" } into temp1

1
Cela a déjà été répondu il y a 9 ans ... quelle valeur apporte cette réponse?
Maciej Jureczko
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.