J'ai une liste des pièces d'identité et de leur prénom, ainsi qu'une liste des pièces d'identité et leur nom de famille. Certaines personnes n'ont pas de prénom et d'autres n'ont pas de nom de famille; Je voudrais faire une jointure externe complète sur les deux listes.
Donc les listes suivantes:
ID FirstName
-- ---------
1 John
2 Sue
ID LastName
-- --------
1 Doe
3 Smith
Devrait produire:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
3 Smith
Je suis nouveau sur LINQ (alors pardonnez-moi si je suis boiteux) et j'ai trouvé pas mal de solutions pour les `` jointures externes LINQ '' qui semblent toutes assez similaires, mais semblent vraiment rester des jointures externes.
Mes tentatives jusqu'à présent vont quelque chose comme ceci:
private void OuterJoinTest()
{
List<FirstName> firstNames = new List<FirstName>();
firstNames.Add(new FirstName { ID = 1, Name = "John" });
firstNames.Add(new FirstName { ID = 2, Name = "Sue" });
List<LastName> lastNames = new List<LastName>();
lastNames.Add(new LastName { ID = 1, Name = "Doe" });
lastNames.Add(new LastName { ID = 3, Name = "Smith" });
var outerJoin = from first in firstNames
join last in lastNames
on first.ID equals last.ID
into temp
from last in temp.DefaultIfEmpty()
select new
{
id = first != null ? first.ID : last.ID,
firstname = first != null ? first.Name : string.Empty,
surname = last != null ? last.Name : string.Empty
};
}
}
public class FirstName
{
public int ID;
public string Name;
}
public class LastName
{
public int ID;
public string Name;
}
Mais cela revient:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
Qu'est-ce que je fais mal?
c#
.net
linq
outer-join
full-outer-join
ninjaPixel
la source
la source
Réponses:
Je ne sais pas si cela couvre tous les cas, logiquement cela semble correct. L'idée est de prendre une jointure externe gauche et une jointure externe droite puis de prendre l'union des résultats.
Cela fonctionne comme écrit car il est dans LINQ to Objects. Si LINQ to SQL ou autre, le processeur de requêtes peut ne pas prendre en charge la navigation sécurisée ou d'autres opérations. Vous devez utiliser l'opérateur conditionnel pour obtenir conditionnellement les valeurs.
c'est à dire,
la source
AsEnumerable()
avant d'effectuer l'union / la concaténation. Essayez cela et voyez comment cela se passe. Si ce n'est pas la route que vous souhaitez emprunter, je ne suis pas sûr de pouvoir vous être plus utile que cela.Mise à jour 1: fournir une méthode d'extension vraiment généralisée
FullOuterJoin
Mise à jour 2: accepter éventuellement une personnalisation
IEqualityComparer
pour le type de cléMise à jour 3 : cette implémentation a récemment fait partie de
MoreLinq
- Merci les gars!Édition ajoutée
FullOuterGroupJoin
( ideone ). J'ai réutilisé leGetOuter<>
implémentation, ce qui en fait une fraction moins performante qu'elle ne pourrait l'être, mais je vise pour le moment un code de `` haut niveau '', pas optimisé à la pointe du progrès.Regardez-le en direct sur http://ideone.com/O36nWc
Imprime la sortie:
Vous pouvez également fournir des valeurs par défaut: http://ideone.com/kG4kqO
Impression:
Explication des termes utilisés:
Rejoindre est un terme emprunté à la conception de bases de données relationnelles:
a
autant de fois qu'il y a d'élémentsb
avec la clé correspondante (c'est-à-dire: rien sib
était vide). Le jargon de la base de données appelle celainner (equi)join
.a
pour lesquels aucun élément correspondant n'existeb
. (c.-à-d. même les résultatsb
étaient vides). Ceci est généralement appeléleft join
.a
ainsi queb
s'il n'existe aucun élément correspondant dans l'autre. (c'est-à-dire même si les résultatsa
étaient vides)Quelque chose que l'on ne voit généralement pas dans le SGBDR est une jointure de groupe [1] :
a
pour plusieurs correspondantsb
, elle regroupe les enregistrements avec les clés correspondantes. C'est souvent plus pratique lorsque vous souhaitez énumérer des enregistrements «joints», sur la base d'une clé commune.Voir aussi GroupJoin qui contient quelques explications de base générales aussi bien.
[1] (je crois qu'Oracle et MSSQL ont des extensions propriétaires pour cela)
Code complet
Une classe d'extension «drop-in» généralisée pour cette
la source
FullOuterJoin
méthode d'extension fournia.GroupBy(selectKeyA).ToDictionary();
fura.ToLookup(selectKeyA)
et àadict.OuterGet(key)
mesurealookup[key]
. Obtenir la remise des clés est un peu plus délicat, si:alookup.Select(x => x.Keys)
.Je pense qu'il y a des problèmes avec la plupart de ceux-ci, y compris la réponse acceptée, car ils ne fonctionnent pas bien avec Linq sur IQueryable soit en raison de trop d'allers-retours sur le serveur et de trop de retours de données, soit de l'exécution excessive du client.
Pour IEnumerable, je n'aime pas la réponse de Sehe ou similaire car elle utilise trop de mémoire (un simple test de 10000000 deux listes a entraîné une perte de mémoire de Linqpad sur ma machine de 32 Go).
En outre, la plupart des autres n'implémentent pas réellement une jointure complète complète appropriée car ils utilisent une union avec une jointure droite au lieu de concaténer avec une anti-semi-jointure droite, ce qui élimine non seulement les lignes de jointure interne en double du résultat, mais tous les doublons appropriés qui existaient à l'origine dans les données de gauche ou de droite.
Voici donc mes extensions qui gèrent tous ces problèmes, génèrent du SQL ainsi que l'implémentation de la jointure dans LINQ to SQL directement, s'exécutant sur le serveur, et sont plus rapides et avec moins de mémoire que d'autres sur Enumerables:
La différence entre une anti-semi-jointure droite est principalement théorique avec Linq to Objects ou dans la source, mais fait une différence côté serveur (SQL) dans la réponse finale, supprimant un
JOIN
.Le codage manuel de
Expression
pour gérer la fusion d'unExpression<Func<>>
dans un lambda pourrait être amélioré avec LinqKit, mais ce serait bien si le langage / compilateur avait ajouté de l'aide pour cela. Les fonctionsFullOuterJoinDistinct
etRightOuterJoin
sont incluses pour être complet, mais je n'ai pas réimplémentéFullOuterGroupJoin
encore réimplémenté.J'ai écrit une autre version d'une jointure externe complète pour
IEnumerable
pour les cas où la clé est commandable, ce qui est environ 50% plus rapide que de combiner la jointure externe gauche avec la demi-jointure anti droite, au moins sur les petites collections. Il passe par chaque collection après avoir été trié une seule fois.J'ai également ajouté une autre réponse pour une version qui fonctionne avec EF en remplaçant le
Invoke
par une extension personnalisée.la source
TP unusedP, TC unusedC
? Sont-ils littéralement inutilisés?TP
,TC
,TResult
pour créer le bonExpression<Func<>>
. Je croyais que je pouvais les remplacer par_
,__
,___
au contraire, mais cela ne semble pas plus clair jusqu'à ce que C # a un caractère générique de paramètre correct à utiliser à la place.The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
. Y a-t-il des restrictions avec ce code? Je veux effectuer un FULL JOIN sur IQueryablesInvoke
par une coutumeExpressionVisitor
pour aligner leInvoke
afin qu'il fonctionne avec EF. Pouvez-vous l'essayer?Voici une méthode d'extension qui fait cela:
la source
Union
supprime les doublons, donc s'il y a des lignes en double dans les données d'origine, elles ne seront pas dans le résultat.Je suppose que l'approche de @ sehe est plus forte, mais jusqu'à ce que je la comprenne mieux, je me retrouve à sauter le pas sur l'extension de @ MichaelSander. Je l'ai modifié pour qu'il corresponde à la syntaxe et au type de retour de la méthode Enumerable.Join () intégrée décrite ici . J'ai ajouté le suffixe "distinct" en ce qui concerne le commentaire de @ cadrell0 sous la solution de @ JeffMercado.
Dans l'exemple, vous l'utiliseriez comme ceci:
À l'avenir, au fur et à mesure que j'apprends, j'ai le sentiment que je vais migrer vers la logique de @ sehe compte tenu de sa popularité. Mais même dans ce cas, je dois faire attention, car je pense qu'il est important d'avoir au moins une surcharge qui correspond à la syntaxe de la méthode ".Join ()" existante si possible, pour deux raisons:
Je suis encore nouveau avec les génériques, les extensions, les instructions Func et d'autres fonctionnalités, donc les commentaires sont certainement les bienvenus.
EDIT: Il ne m'a pas fallu longtemps pour réaliser qu'il y avait un problème avec mon code. Je faisais un .Dump () dans LINQPad et regardais le type de retour. C'était juste IEnumerable, alors j'ai essayé de le faire correspondre. Mais quand j'ai fait un .Where () ou .Select () sur mon extension, j'ai eu une erreur: "'System Collections.IEnumerable' ne contient pas de définition pour 'Select' et ...". Donc, à la fin, j'ai pu faire correspondre la syntaxe d'entrée de .Join (), mais pas le comportement de retour.
EDIT: Ajout de "TResult" au type de retour pour la fonction. Manqué que lors de la lecture de l'article Microsoft, et bien sûr, cela a du sens. Avec ce correctif, il semble maintenant que le comportement de retour soit conforme à mes objectifs après tout.
la source
Comme vous l'avez trouvé, Linq n'a pas de construction de "jointure externe". Le plus proche que vous pouvez obtenir est une jointure externe gauche en utilisant la requête que vous avez indiquée. Pour cela, vous pouvez ajouter tous les éléments de la liste des noms qui ne sont pas représentés dans la jointure:
la source
J'aime la réponse de sehe, mais elle n'utilise pas d'exécution différée (les séquences d'entrée sont énormément énumérées par les appels à ToLookup). Donc, après avoir regardé les sources .NET pour LINQ-to-objects , j'ai trouvé ceci:
Cette implémentation a les propriétés importantes suivantes:
Ces propriétés sont importantes, car elles sont ce à quoi s'attendent les débutants de FullOuterJoin mais expérimentés avec LINQ.
la source
J'ai décidé d'ajouter ceci comme une réponse distincte car je ne suis pas certain qu'il soit suffisamment testé. Il s'agit d'une réimplémentation de la
FullOuterJoin
méthode utilisant essentiellement une version simplifiée et personnalisée deLINQKit
Invoke
/Expand
forExpression
afin qu'elle fonctionne avec Entity Framework. Il n'y a pas beaucoup d'explications car c'est à peu près la même que ma réponse précédente.la source
base.Visit(node)
ne devrait pas lever d'exception car cela revient simplement dans l'arbre. Je peux accéder à pratiquement n'importe quel service de partage de code, mais pas configurer une base de données de test. L'exécuter avec mon test LINQ to SQL semble cependant fonctionner correctement.Guid
clé à une cléGuid?
étrangère?Effectue une énumération de streaming en mémoire sur les deux entrées et appelle le sélecteur pour chaque ligne. S'il n'y a pas de corrélation à l'itération en cours, l' un des arguments du sélecteur sera nul .
Exemple:
Nécessite un IComparer pour le type de corrélation, utilise Comparer.Default s'il n'est pas fourni.
Nécessite que «OrderBy» soit appliqué aux énumérateurs d'entrée
la source
OrderBy
sur les deux projections clés.OrderBy
tampon la séquence entière, pour les raisons évidentes .Ma solution propre pour la situation de cette clé est unique dans les deux énumérables:
alors
les sorties:
la source
Jointure externe complète pour deux tables ou plus: commencez par extraire la colonne sur laquelle vous souhaitez vous joindre.
Utilisez ensuite la jointure externe gauche entre la colonne extraite et les tables principales.
la source
J'ai écrit cette classe d'extensions pour une application il y a peut-être 6 ans, et je l'utilise depuis dans de nombreuses solutions sans problèmes. J'espère que ça aide.
edit: j'ai remarqué que certains ne savaient pas comment utiliser une classe d'extension.
Pour utiliser cette classe d'extension, il suffit de référencer son espace de noms dans votre classe en ajoutant la ligne suivante à l'aide de joinext;
^ cela devrait vous permettre de voir l'intellisense des fonctions d'extension sur n'importe quelle collection d'objets IEnumerable que vous utilisez.
J'espère que cela t'aides. Faites-moi savoir si ce n'est toujours pas clair, et j'espère écrire un exemple d'exemple sur la façon de l'utiliser.
Voici maintenant la classe:
la source
SelectMany
ne puisse pas être convertie en une arborescence d'expression digne de LINQ2SQL, semble-t-il.Je pense que la clause de jointure LINQ n'est pas la bonne solution à ce problème, car le but de la clause de jointure n'est pas d'accumuler des données de la manière requise pour cette solution de tâche. Le code pour fusionner les collections séparées créées devient trop compliqué, peut-être est-il correct à des fins d'apprentissage, mais pas pour de vraies applications. L'une des façons de résoudre ce problème est dans le code ci-dessous:
Si les collections réelles sont grandes pour la formation HashSet, les boucles foreach peuvent être utilisées à la place, le code ci-dessous:
la source
Merci à tous pour les articles intéressants!
J'ai modifié le code car dans mon cas j'avais besoin
Pour ceux intéressés c'est mon code modifié (en VB, désolé)
la source
Encore une autre jointure externe complète
Comme je n'étais pas très satisfait de la simplicité et de la lisibilité des autres propositions, je me suis retrouvé avec ceci:
Il n'a pas la prétention d'être rapide (environ 800 ms pour rejoindre 1000 * 1000 sur un processeur 2020m: 2.4ghz / 2cores). Pour moi, c'est juste une jointure extérieure complète compacte et décontractée.
Il fonctionne de la même manière qu'un SQL FULL OUTER JOIN (conservation des doublons)
À votre santé ;-)
L'idée est de
Voici un test succinct qui va avec:
Placez un point d'arrêt à la fin pour vérifier manuellement qu'il se comporte comme prévu
}
la source
Je déteste vraiment ces expressions linq, c'est pourquoi SQL existe:
Créez ceci en tant que vue SQL dans la base de données et importez-le en tant qu'entité.
Bien sûr, l'union (distincte) des jointures gauche et droite le fera aussi, mais c'est stupide.
la source