Левое внешнее соединение, в чем разница между этими двумя подходами?
В чем разница между этими двумя подходами для выполнения левого внешнего соединения с 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 ответа:
Хорошо. Из того, что я вижу: (а)
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 анонимных типов, каждый из которых представляет пару поставщика и покупателя.
Но то, что вы решили сделать, - это присоединиться к левым, как написано в вашем названии. Что он делает, он берет каждый элемент в списке, созданном в сниппете (A) и сопоставляет емуvar suppliersAndBuyers = from s in suppliers orderby s.District join b in buyers on s.District equals b.District into buyersGroup
IEnumerable
всех соответствующих объектов из списка покупателей. Это приводит к перечислению совпадений. Например, для Harrison , первой записи в спискеsuppliers
, вы получитеIEnumerable
содержащий Джонни, Пол и Пирс. То же самое для других элементов в спискеsuppliers
, упорядоченных по ихDistrict
.И это причина, почему вы заканчиваете с
IEnumerable<IEnumerable<Buyer>>
. Потому что для каждого поставщика (первое IEnumerable измерение) у вас есть "список"Buyer
(второе измерение + тип объяснено)Тогда объединение пустых записей устарело на мой взгляд, потому что у вас не должно быть
null
s, а просто пустыеIEnumerable
s, тогда как итерация через них вы просто не попадете ни в один элемент. (Хотя я не уверен в последнем абзаце, поскольку я никогда не компилировал код, поэтому я не знаю)
Теперь, что касается коалесцирующей части, первый пример создает новый объектBuyer
Для каждой из записей, а затем принимаетDefaultIsEmpty
. Во втором примере сначала создаются первое измерение и второе измерение с полными IEnumerables, а затем при повторной итерации объединяются пустые значения. Который, как я уже упоминал в комментариях, один петли невостребованными.
В первом вы возвращаете
IEnumerable
изBuyer
из-заnew Buyer()
, а во втором вы возвращаетеIEnumerable
из анонимных типов из-заselect new
. У них есть свои плюсы и минусы, Преимущества и недостатки.Вы можете проверить следующие ответы, чтобы решить:
Итак, мне потребовалось некоторое время, чтобы расшифровать это:)
Итак, дело в том, что в первом примере Вы делаете
Выберите 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 и тем, что такое эта мера и вес, применяемый к ней, зависит от этой ситуации.