Левое внешнее соединение, в чем разница между этими двумя подходами?


В чем разница между этими двумя подходами для выполнения левого внешнего соединения с LINQ, так как я использую два списка покупателей и поставщиков и объединяю их общим районом, чтобы найти поставщиков и покупателей, которые находятся в одном и том же районе.

class Supplier
{
    public string Name { get; set; }
    public string District { get; set; }
}

class Buyer
{
    public string Name { get; set; }
    public string District { get; set; }
}
List<Buyer> buyers = new List<Buyer>()
{
    new Buyer() { Name = "Johny", District = "Fantasy District" },
    new Buyer() { Name = "Peter", District = "Scientists District" },
    new Buyer() { Name = "Paul", District = "Fantasy District" },
    new Buyer() { Name = "Maria", District = "Scientists District" },
    new Buyer() { Name = "Joshua", District = "EarthIsFlat District" },
    new Buyer() { Name = "Sylvia", District = "Developers District" },
    new Buyer() { Name = "Rebecca", District = "Scientists District" },
    new Buyer() { Name = "Jaime", District = "Developers District" },
    new Buyer() { Name = "Pierce", District = "Fantasy District" }
};
List<Supplier> suppliers = new List<Supplier>()
{
    new Supplier() { Name = "Harrison", District = "Fantasy District" },
    new Supplier() { Name = "Charles", District = "Developers District" },
    new Supplier() { Name = "Hailee", District = "Scientists District" },
    new Supplier() { Name = "Taylor", District = "EarthIsFlat District" }
};

Первый:

var suppliersAndBuyers = from s in suppliers
                         orderby s.District
                         join b in buyers on s.District equals b.District into buyersGroup
                         select buyersGroup.DefaultIfEmpty(
                             new Buyer()
                             {
                                 Name = string.Empty,
                                 District = s.District
                             });

foreach (var item in suppliersAndBuyers)
{
    foreach (var buyer in item)
    {
        Console.WriteLine($"{buyer.District} {buyer.Name}");
    }
}

И второй подход:

var suppliersAndBuyers = from s in suppliers
                                 orderby s.District
                                 join b in buyers on s.District equals b.District into buyersGroup
                                 from bG in buyersGroup.DefaultIfEmpty()
                                 select new
                                 {
                                     Name = bG.Name == null ? string.Empty : bG.Name,
                                     s.District,
                                 };

foreach (var item in suppliersAndBuyers)
{
    Console.WriteLine($"{item.District} {item.Name}");
}

Оба производят точно такой же выход, единственная разница в том, как мы выводим результаты? Какой из них я должен использовать?

Редактировать: первый подход возвращает IEnumerable<IEnumerable<Buyer>> второй возвращает IEnumerable<AnonymousType>, является ли единственным значимым различием между ними в том, какой тип они возвращают, и является ли это единственным решающим фактором между двумя подходами, хочу ли я тип или Анонимный тип?

4 3

4 ответа:

Хорошо. Из того, что я вижу: (а)

var suppliersAndBuyers = from s in suppliers
                         orderby s.District

Перечисляет список поставщиков. Это же очевидно. Теперь, присоединив его к списку покупателей:

var suppliersAndBuyers = from s in suppliers
                         orderby s.District
                         join b in buyers on s.District equals b.District

Это создает совпадения (некоторые объекты, которые я не знаю типа, так как у меня нет нормального экземпляра Visual Studio передо мной). Но, например, это похоже на Harrison:Jonnie, Hailee:Peter, .... Теперь мы можем создать IEnumerable объектов на основе этих соответствий (представленных переменными b и s) следующим образом:

var suppliersAndBuyers = from s in suppliers
                         orderby s.District
                         join b in buyers on s.District equals b.District
                         select new {
                             Supplier = s, Buyer = b
                         }

Это будет создайте IEnumerable анонимных типов, каждый из которых представляет пару поставщика и покупателя.

var suppliersAndBuyers = from s in suppliers
                         orderby s.District
                         join b in buyers on s.District equals b.District into buyersGroup
Но то, что вы решили сделать, - это присоединиться к левым, как написано в вашем названии. Что он делает, он берет каждый элемент в списке, созданном в сниппете (A) и сопоставляет ему IEnumerable всех соответствующих объектов из списка покупателей. Это приводит к перечислению совпадений. Например, для Harrison , первой записи в списке suppliers, вы получите IEnumerable содержащий Джонни, Пол и Пирс. То же самое для других элементов в списке suppliers, упорядоченных по их District.

И это причина, почему вы заканчиваете с IEnumerable<IEnumerable<Buyer>>. Потому что для каждого поставщика (первое IEnumerable измерение) у вас есть "список" Buyer (второе измерение + тип объяснено)

Тогда объединение пустых записей устарело на мой взгляд, потому что у вас не должно быть nulls, а просто пустые IEnumerables, тогда как итерация через них вы просто не попадете ни в один элемент. (Хотя я не уверен в последнем абзаце, поскольку я никогда не компилировал код, поэтому я не знаю)


Теперь, что касается коалесцирующей части, первый пример создает новый объект Buyer Для каждой из записей, а затем принимает DefaultIsEmpty. Во втором примере сначала создаются первое измерение и второе измерение с полными IEnumerables, а затем при повторной итерации объединяются пустые значения. Который, как я уже упоминал в комментариях, один петли невостребованными.

В первом вы возвращаете IEnumerable из Buyer из-за new Buyer(), а во втором вы возвращаете IEnumerable из анонимных типов из-за select new. У них есть свои плюсы и минусы, Преимущества и недостатки.

Вы можете проверить следующие ответы, чтобы решить:

Https://stackoverflow.com/a/21443164/2946329

Https://stackoverflow.com/a/48677/2946329

Итак, мне потребовалось некоторое время, чтобы расшифровать это:)

Итак, дело в том, что в первом примере Вы делаете

Выберите buyersGroup.Метода defaultifempty( новый покупатель() { Имя = строка.Пустой, Район = s. район });

Смысл этой строки таков... выберите все группы покупателей и, если она пуста, верните new Buyer() { Name = string.Empty, District = s.District } (как вы определили его по умолчанию)

Во втором примере

Из bG в buyersGroup.Метода defaultifempty() выберите Создать { Имя = bG.Name = = ноль ? строка.Пустой : БГ.Название, С. район, };

Вы сначала определяете значение по умолчанию, если группа является пустой buyersGroup.DefaultIfEmpty () и только после этого делать выбор. Обратите пристальное внимание на то, что заключено в скобки DefaultIfEmpty.

Править

Я, кажется, не могу найти причину, чтобы иметь DefaultIfEmpty вообще... Не думайте, что вы можете получить null на левой стороне, чтобы нуждаться в нем... И это может немного облегчить понимание кода.

См. Перечисляемые.DefaultIfEmpty . В первом примере вы передаете new Buyer этому методу расширения в качестве значения по умолчанию, возвращаемого в соединении, если экземпляр Buyer не может быть найден в соединении (т. е. ваше внешнее соединение). После этого не существует оператора select, поэтому результат соединения моделируется в вашем результате как IEnumerable<IEnumerable<Buyer>>.

Ваш второй запрос использует select с анонимной проекцией, поэтому он приводит к сплющенной коллекции.

Просто глядя на ваш результирующий Тип

  • Вы можете имитировать результаты первого запроса, удалив select во втором запросе

    from s in suppliers
    orderby s.District
    join b in buyers on s.District equals b.District into buyersGroup
    from bG in buyersGroup.DefaultIfEmpty();
    
  • Чтобы пойти в другом направлении,вы можете добавить ту же инструкцию select из 2-го запроса в конце первого.

    from s in suppliers
    orderby s.District
    join b in buyers on s.District equals b.District into buyersGroup
    select buyersGroup.DefaultIfEmpty(
        new Buyer()
        {
            Name = string.Empty,
            District = s.District
        ))
    select new
    {
        Name = bG.Name == null ? string.Empty : bG.Name,
        s.District,
    };
    

Что касается того, почему использовать одно над другим, то это вопрос мнения и также зависит от ситуации. В вашем очень простом примере без какого-либо другого контекста использование проекции было бы следующим: более понятный ответ. Если вы хотите передать результат другому методу или обратно из метода, в котором он выполняется, то IEnumerable<IEnumerable<Buyer>> будет единственным способом сделать это (, если он ограничен этими 2 образцами). Если вы должны были сделать это с хранилищем данных, то я бы рекомендовал вам профилировать запросы, чтобы увидеть, что запрос был выполнен, и профилировать это. Короче говоря, нет правильного / неправильного ответа, пока у вас нет конкретной ситуации в реальном мире, где есть измеримый разница между этими 2 и тем, что такое эта мера и вес, применяемый к ней, зависит от этой ситуации.