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

johnc
la source
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.

Jon Skeet
la source
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 }
KristoferA
la source
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.

RealNapster
la source
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).

niieani
la source
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 ().

Alexei
la source
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
              };
user3966657
la source
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......};
Perpetualcoder
la source
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.

tvanfosson
la source
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});
Mahesh
la source
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();
Adam Garner
la source
-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

user2745564
la source
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
Ven
la source
1
Cela a déjà été répondu il y a 9 ans ... quelle valeur apporte cette réponse?
Maciej Jureczko