ЛЕВЫЙ НАРУЖНЫЙ ПРИСОЕДИНЯЙТЕСЬ В LINQ - PullRequest
474 голосов
/ 04 августа 2010

Как выполнить левое внешнее объединение в C # LINQ для объектов без использования предложений join-on-equals-into? Есть ли способ сделать это с помощью where? Правильная проблема: Для внутреннего соединения легко, и у меня есть решение, подобное этому

List<JoinPair> innerFinal = (from l in lefts from r in rights where l.Key == r.Key
                             select new JoinPair { LeftId = l.Id, RightId = r.Id})

но для левого внешнего соединения мне нужно решение. Мой что-то вроде этого, но он не работает

List< JoinPair> leftFinal = (from l in lefts from r in rights
                             select new JoinPair { 
                                            LeftId = l.Id, 
                                            RightId = ((l.Key==r.Key) ? r.Id : 0
                                        })

, где JoinPair является классом:

public class JoinPair { long leftId; long rightId; }

Ответы [ 20 ]

544 голосов
/ 05 августа 2010

Как указано в:

101 Образцы LINQ - Левое внешнее соединение

var q =
    from c in categories
    join p in products on c.Category equals p.Category into ps
    from p in ps.DefaultIfEmpty()
    select new { Category = c, ProductName = p == null ? "(No products)" : p.ProductName };
483 голосов
/ 09 мая 2014

Если используется поставщик LINQ, управляемый базой данных, значительно более читабельное левое внешнее объединение можно записать так:

from maintable in Repo.T_Whatever 
from xxx in Repo.T_ANY_TABLE.Where(join condition).DefaultIfEmpty()

Если вы пропустите DefaultIfEmpty(), у вас будет внутреннее объединение.

Возьмите принятый ответ:

  from c in categories
    join p in products on c equals p.Category into ps
    from p in ps.DefaultIfEmpty()

Этот синтаксис очень запутанный, и неясно, как он работает, когда вы хотите покинуть несколько таблиц MULTIPLE.

Примечание Следует отметить, что from alias in Repo.whatever.Where(condition).DefaultIfEmpty() - это то же самое, что и external-apply / left-join-lateral, который любой (приличный) оптимизатор базы данных вполне способен преобразовать в левое соединение, если вы не введете-row-values ​​(иначе фактическое внешнее применение).Не делайте этого в Linq-2-Objects (потому что при использовании Linq-to-Objects нет оптимизатора DB).

Подробный пример

var query2 = (
    from users in Repo.T_User
    from mappings in Repo.T_User_Group
         .Where(mapping => mapping.USRGRP_USR == users.USR_ID)
         .DefaultIfEmpty() // <== makes join left join
    from groups in Repo.T_Group
         .Where(gruppe => gruppe.GRP_ID == mappings.USRGRP_GRP)
         .DefaultIfEmpty() // <== makes join left join

    // where users.USR_Name.Contains(keyword)
    // || mappings.USRGRP_USR.Equals(666)  
    // || mappings.USRGRP_USR == 666 
    // || groups.Name.Contains(keyword)

    select new
    {
         UserId = users.USR_ID
        ,UserName = users.USR_User
        ,UserGroupId = groups.ID
        ,GroupName = groups.Name
    }

);


var xy = (query2).ToList();

При использовании с LINQ 2 SQL он будет хорошо переведен в следующий очень разборчивый SQL-запрос:

SELECT 
     users.USR_ID AS UserId 
    ,users.USR_User AS UserName 
    ,groups.ID AS UserGroupId 
    ,groups.Name AS GroupName 
FROM T_User AS users

LEFT JOIN T_User_Group AS mappings
   ON mappings.USRGRP_USR = users.USR_ID

LEFT JOIN T_Group AS groups
    ON groups.GRP_ID == mappings.USRGRP_GRP

Редактировать:

См. Также " Преобразование SQLЗапрос сервера к запросу Linq"для более сложного примера.

Кроме того, если вы делаете это в Linq-2-Objects (вместо Linq-2-SQL), вы должны сделать это старомодным способом (потому что LINQ to SQL переводит это правильно для операций объединения, но поверх объектов этот метод вызывает полное сканирование и не использует поиск по индексу, как бы то ни было ...):

    var query2 = (
    from users in Repo.T_Benutzer
    join mappings in Repo.T_Benutzer_Benutzergruppen on mappings.BEBG_BE equals users.BE_ID into tmpMapp
    join groups in Repo.T_Benutzergruppen on groups.ID equals mappings.BEBG_BG into tmpGroups
    from mappings in tmpMapp.DefaultIfEmpty()
    from groups in tmpGroups.DefaultIfEmpty()
    select new
    {
         UserId = users.BE_ID
        ,UserName = users.BE_User
        ,UserGroupId = mappings.BEBG_BG
        ,GroupName = groups.Name
    }

);
114 голосов
/ 05 февраля 2014

Использование лямбда-выражения

db.Categories    
  .GroupJoin(
      db.Products,
      Category => Category.CategoryId,
      Product => Product.CategoryId,
      (x, y) => new { Category = x, Products = y })
  .SelectMany(
      xy => xy.Products.DefaultIfEmpty(),
      (x, y) => new { Category = x.Category, Product = y })
  .Select(s => new
  {
      CategoryName = s.Category.Name,     
      ProductName = s.Product.Name   
  })
39 голосов
/ 05 августа 2010

Взгляните на этот пример . Этот запрос должен работать:

var leftFinal = from left in lefts
                join right in rights on left equals right.Left into leftRights
                from leftRight in leftRights.DefaultIfEmpty()
                select new { LeftId = left.Id, RightId = left.Key==leftRight.Key ? leftRight.Id : 0 };
37 голосов
/ 18 августа 2016

Теперь в качестве метода расширения:

public static class LinqExt
{
    public static IEnumerable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(this IEnumerable<TLeft> left, IEnumerable<TRight> right, Func<TLeft, TKey> leftKey, Func<TRight, TKey> rightKey,
        Func<TLeft, TRight, TResult> result)
    {
        return left.GroupJoin(right, leftKey, rightKey, (l, r) => new { l, r })
             .SelectMany(
                 o => o.r.DefaultIfEmpty(),
                 (l, r) => new { lft= l.l, rght = r })
             .Select(o => result.Invoke(o.lft, o.rght));
    }
}

Используйте так, как вы обычно используете join:

var contents = list.LeftOuterJoin(list2, 
             l => l.country, 
             r => r.name,
            (l, r) => new { count = l.Count(), l.country, l.reason, r.people })

Надеюсь, это сэкономит вам время.

17 голосов
/ 04 марта 2014

Реализация левого внешнего соединения методами расширения может выглядеть как

public static IEnumerable<Result> LeftJoin<TOuter, TInner, TKey, Result>(
  this IEnumerable<TOuter> outer, IEnumerable<TInner> inner
  , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
  , Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
  {
    if (outer == null)
      throw new ArgumentException("outer");

    if (inner == null)
      throw new ArgumentException("inner");

    if (outerKeySelector == null)
      throw new ArgumentException("outerKeySelector");

    if (innerKeySelector == null)
      throw new ArgumentException("innerKeySelector");

    if (resultSelector == null)
      throw new ArgumentException("resultSelector");

    return LeftJoinImpl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer ?? EqualityComparer<TKey>.Default);
  }

  static IEnumerable<Result> LeftJoinImpl<TOuter, TInner, TKey, Result>(
      IEnumerable<TOuter> outer, IEnumerable<TInner> inner
      , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
      , Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
  {
    var innerLookup = inner.ToLookup(innerKeySelector, comparer);

    foreach (var outerElment in outer)
    {
      var outerKey = outerKeySelector(outerElment);
      var innerElements = innerLookup[outerKey];

      if (innerElements.Any())
        foreach (var innerElement in innerElements)
          yield return resultSelector(outerElment, innerElement);
      else
        yield return resultSelector(outerElment, default(TInner));
     }
   }

Затем селектор результатов должен позаботиться о нулевых элементах. Fx.

   static void Main(string[] args)
   {
     var inner = new[] { Tuple.Create(1, "1"), Tuple.Create(2, "2"), Tuple.Create(3, "3") };
     var outer = new[] { Tuple.Create(1, "11"), Tuple.Create(2, "22") };

     var res = outer.LeftJoin(inner, item => item.Item1, item => item.Item1, (it1, it2) =>
     new { Key = it1.Item1, V1 = it1.Item2, V2 = it2 != null ? it2.Item2 : default(string) });

     foreach (var item in res)
       Console.WriteLine(string.Format("{0}, {1}, {2}", item.Key, item.V1, item.V2));
   }
10 голосов
/ 13 октября 2016

взгляните на этот пример

class Person
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Phone { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

public static void LeftOuterJoinExample()
{
    Person magnus = new Person {ID = 1, FirstName = "Magnus", LastName = "Hedlund"};
    Person terry = new Person {ID = 2, FirstName = "Terry", LastName = "Adams"};
    Person charlotte = new Person {ID = 3, FirstName = "Charlotte", LastName = "Weiss"};
    Person arlene = new Person {ID = 4, FirstName = "Arlene", LastName = "Huff"};

    Pet barley = new Pet {Name = "Barley", Owner = terry};
    Pet boots = new Pet {Name = "Boots", Owner = terry};
    Pet whiskers = new Pet {Name = "Whiskers", Owner = charlotte};
    Pet bluemoon = new Pet {Name = "Blue Moon", Owner = terry};
    Pet daisy = new Pet {Name = "Daisy", Owner = magnus};

    // Create two lists.
    List<Person> people = new List<Person> {magnus, terry, charlotte, arlene};
    List<Pet> pets = new List<Pet> {barley, boots, whiskers, bluemoon, daisy};

    var query = from person in people
        where person.ID == 4
        join pet in pets on person equals pet.Owner  into personpets
        from petOrNull in personpets.DefaultIfEmpty()
        select new { Person=person, Pet = petOrNull}; 



    foreach (var v in query )
    {
        Console.WriteLine("{0,-15}{1}", v.Person.FirstName + ":", (v.Pet == null ? "Does not Exist" : v.Pet.Name));
    }
}

// This code produces the following output:
//
// Magnus:        Daisy
// Terry:         Barley
// Terry:         Boots
// Terry:         Blue Moon
// Charlotte:     Whiskers
// Arlene:

теперь вы можете include elements from the left, даже если этот элемент has no matches in the right, в нашем случае мы получили Arlene, даже если у него нет соответствия в правом

вот ссылка

Как выполнить левое внешнее соединение (Руководство по программированию в C #)

9 голосов
/ 04 июля 2017

Это общая форма (как уже указано в других ответах)

var c =
    from a in alpha
    join b in beta on b.field1 equals a.field1 into b_temp
    from b_value in b_temp.DefaultIfEmpty()
    select new { Alpha = a, Beta = b_value };

Однако вот объяснение, которое, я надеюсь, прояснит, что это на самом деле означает!

join b in beta on b.field1 equals a.field1 into b_temp

по существу создаетотдельный результирующий набор b_temp, который фактически содержит нулевые «строки» для записей справа (записи в «b»).

Затем следующая строка:

from b_value in b_temp.DefaultIfEmpty()

.. повторяетсяповерх этого результирующего набора, установив нулевое значение по умолчанию для «строки» с правой стороны, и установив результат соединения правой строки со значением «b_value» (то есть значением, которое находится с правой стороны,если есть совпадающая запись, или 'null', если ее нет).

Теперь, если правая часть является результатом отдельного запроса LINQ, она будет состоять из анонимных типов, которые могут толькобыть «что-то» или «ноль».Однако, если он перечислим (например, List - где MyObjectB является классом с 2 полями), то можно быть конкретным относительно того, какие значения по умолчанию 'null' используются для его свойств:

var c =
    from a in alpha
    join b in beta on b.field1 equals a.field1 into b_temp
    from b_value in b_temp.DefaultIfEmpty( new MyObjectB { Field1 = String.Empty, Field2 = (DateTime?) null })
    select new { Alpha = a, Beta_field1 = b_value.Field1, Beta_field2 = b_value.Field2 };

Это гарантируетсамо по себе 'b' не равно нулю (но его свойства могут быть нулевыми, используя заданные вами значения по умолчанию), и это позволяет вам проверять свойства b_value без получения исключения нулевой ссылки для b_value.Обратите внимание, что для обнуляемого DateTime тип (DateTime?), То есть «обнуляемый DateTime» должен быть указан как «Тип» нулевого значения в спецификации для «DefaultIfEmpty» (это также будет применяться к типам, которые не являются «изначально»'nullable, например, double, float).

Вы можете выполнить несколько левых внешних объединений, просто связав приведенный выше синтаксис.

8 голосов
/ 21 апреля 2015

Вот пример, если вам нужно объединить более двух таблиц:

from d in context.dc_tpatient_bookingd
join bookingm in context.dc_tpatient_bookingm 
     on d.bookingid equals bookingm.bookingid into bookingmGroup
from m in bookingmGroup.DefaultIfEmpty()
join patient in dc_tpatient
     on m.prid equals patient.prid into patientGroup
from p in patientGroup.DefaultIfEmpty()

Ссылка: https://stackoverflow.com/a/17142392/2343

4 голосов
/ 01 октября 2017

Метод расширения, который работает как левое соединение с синтаксисом соединения

public static class LinQExtensions
{
    public static IEnumerable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
        this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, 
        Func<TOuter, TKey> outerKeySelector, 
        Func<TInner, TKey> innerKeySelector, 
        Func<TOuter, TInner, TResult> resultSelector)
    {
        return outer.GroupJoin(
            inner, 
            outerKeySelector, 
            innerKeySelector,
            (outerElement, innerElements) => resultSelector(outerElement, innerElements.FirstOrDefault()));
    }
}

, только что написал его в ядре .NET и, похоже, работает как положено.

Маленький тест:

        var Ids = new List<int> { 1, 2, 3, 4};
        var items = new List<Tuple<int, string>>
        {
            new Tuple<int, string>(1,"a"),
            new Tuple<int, string>(2,"b"),
            new Tuple<int, string>(4,"d"),
            new Tuple<int, string>(5,"e"),
        };

        var result = Ids.LeftJoin(
            items,
            id => id,
            item => item.Item1,
            (id, item) => item ?? new Tuple<int, string>(id, "not found"));

        result.ToList()
        Count = 4
        [0]: {(1, a)}
        [1]: {(2, b)}
        [2]: {(3, not found)}
        [3]: {(4, d)}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...