256 votes

LINQ - Jointure externe complète

J'ai une liste d'identifiants de personnes et de leur prénom, et une liste d'identifiants de personnes et de leur nom de famille. Certaines personnes n'ont pas de prénom et d'autres n'ont pas de nom de famille ; j'aimerais 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 dans le domaine de LINQ (donc pardonnez-moi si je suis nul) et j'ai trouvé plusieurs solutions pour les "LINQ Outer Joins" qui se ressemblent toutes, mais qui semblent être des jointures externes gauches.

Mes tentatives jusqu'à présent ressemblent à 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 ceci revient :

ID  FirstName  LastName
--  ---------  --------
 1  John       Doe
 2  Sue

Qu'est-ce que je fais de mal ?

2 votes

Avez-vous besoin que cela fonctionne pour les listes en mémoire uniquement, ou pour Linq2Sql ?

0 votes

0voto

Julien R Points 68

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, j'ai abouti à celle-ci :

Il n'a pas la prétention d'être rapide (environ 800 ms pour joindre 1000 * 1000 sur un CPU 2020m : 2.4ghz / 2cores). Pour moi, c'est juste une jointure externe complète, compacte et occasionnelle.

Cela fonctionne de la même manière qu'un FULL OUTER JOIN SQL (conservation des doublons).

Santé ;-)

using System;
using System.Collections.Generic;
using System.Linq;
namespace NS
{
public static class DataReunion
{
    public static List<Tuple<T1, T2>> FullJoin<T1, T2, TKey>(List<T1> List1, Func<T1, TKey> KeyFunc1, List<T2> List2, Func<T2, TKey> KeyFunc2)
    {
        List<Tuple<T1, T2>> result = new List<Tuple<T1, T2>>();

        Tuple<TKey, T1>[] identifiedList1 = List1.Select(_ => Tuple.Create(KeyFunc1(_), _)).OrderBy(_ => _.Item1).ToArray();
        Tuple<TKey, T2>[] identifiedList2 = List2.Select(_ => Tuple.Create(KeyFunc2(_), _)).OrderBy(_ => _.Item1).ToArray();

        identifiedList1.Where(_ => !identifiedList2.Select(__ => __.Item1).Contains(_.Item1)).ToList().ForEach(_ => {
            result.Add(Tuple.Create<T1, T2>(_.Item2, default(T2)));
        });

        result.AddRange(
            identifiedList1.Join(identifiedList2, left => left.Item1, right => right.Item1, (left, right) => Tuple.Create<T1, T2>(left.Item2, right.Item2)).ToList()
        );

        identifiedList2.Where(_ => !identifiedList1.Select(__ => __.Item1).Contains(_.Item1)).ToList().ForEach(_ => {
            result.Add(Tuple.Create<T1, T2>(default(T1), _.Item2));
        });

        return result;
    }
}
}

L'idée est de

  1. Construire des identités basées sur les constructeurs de fonctions clés fournis.
  2. Traiter uniquement les éléments de gauche
  3. Traitement de la jointure interne
  4. Traiter uniquement les éléments de droit

Voici un test succinct qui l'accompagne :

Placez un point d'arrêt à la fin pour vérifier manuellement qu'il se comporte comme prévu.

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NS;

namespace Tests
{
[TestClass]
public class DataReunionTest
{
    [TestMethod]
    public void Test()
    {
        List<Tuple<Int32, Int32, String>> A = new List<Tuple<Int32, Int32, String>>();
        List<Tuple<Int32, Int32, String>> B = new List<Tuple<Int32, Int32, String>>();

        Random rnd = new Random();

        /* Comment the testing block you do not want to run
        /* Solution to test a wide range of keys*/

        for (int i = 0; i < 500; i += 1)
        {
            A.Add(Tuple.Create(rnd.Next(1, 101), rnd.Next(1, 101), "A"));
            B.Add(Tuple.Create(rnd.Next(1, 101), rnd.Next(1, 101), "B"));
        }

        /* Solution for essential testing*/

        A.Add(Tuple.Create(1, 2, "B11"));
        A.Add(Tuple.Create(1, 2, "B12"));
        A.Add(Tuple.Create(1, 3, "C11"));
        A.Add(Tuple.Create(1, 3, "C12"));
        A.Add(Tuple.Create(1, 3, "C13"));
        A.Add(Tuple.Create(1, 4, "D1"));

        B.Add(Tuple.Create(1, 1, "A21"));
        B.Add(Tuple.Create(1, 1, "A22"));
        B.Add(Tuple.Create(1, 1, "A23"));
        B.Add(Tuple.Create(1, 2, "B21"));
        B.Add(Tuple.Create(1, 2, "B22"));
        B.Add(Tuple.Create(1, 2, "B23"));
        B.Add(Tuple.Create(1, 3, "C2"));
        B.Add(Tuple.Create(1, 5, "E2"));

        Func<Tuple<Int32, Int32, String>, Tuple<Int32, Int32>> key = (_) => Tuple.Create(_.Item1, _.Item2);

        var watch = System.Diagnostics.Stopwatch.StartNew();
        var res = DataReunion.FullJoin(A, key, B, key);
        watch.Stop();
        var elapsedMs = watch.ElapsedMilliseconds;
        String aser = JToken.FromObject(res).ToString(Formatting.Indented);
        Console.Write(elapsedMs);
    }
}

}

-3voto

Milan Švec Points 1447

Je déteste vraiment ces expressions linq, c'est pourquoi SQL existe :

select isnull(fn.id, ln.id) as id, fn.firstname, ln.lastname
   from firstnames fn
   full join lastnames ln on ln.id=fn.id

Créez cette vue sql dans la base de données et importez-la en tant qu'entité.

Bien sûr, l'union (distincte) des jointures de gauche et de droite le fera aussi, mais c'est stupide.

13 votes

Pourquoi ne pas laisser tomber autant d'abstractions que possible et faire cela en code machine ? (Indice : parce que les abstractions d'ordre supérieur facilitent la vie du programmeur). Cela ne répond pas à la question et ressemble plus à une diatribe contre LINQ.

9 votes

Qui a dit que les données provenaient d'une base de données ?

1 votes

Bien sûr, il s'agit d'une base de données, il y a des mots "outer join" en question :) google.cz/search?q=outer+jointer

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X