У меня есть список удостоверений личности и их имени, а также список удостоверений личности и их фамилии. У некоторых людей нет имени, а у некоторых нет фамилии; Я хотел бы сделать полное внешнее объединение в двух списках.
Итак, следующие списки:
ID FirstName
-- ---------
1 John
2 Sue
ID LastName
-- --------
1 Doe
3 Smith
Должен производить:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
3 Smith
Я новичок в LINQ (так что извините меня, если я хромаю) и нашел немало решений для 'LINQ Outer Joins', которые выглядят очень похоже, но на самом деле кажутся оставленными внешними объединениями.
Мои попытки пока идут примерно так:
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;
}
Но это возвращает:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
Что я делаю не так?
c#
.net
linq
outer-join
full-outer-join
ninjaPixel
источник
источник
Ответы:
Я не знаю, охватывает ли это все случаи, логически это кажется правильным. Идея состоит в том, чтобы взять левое внешнее соединение и правое внешнее соединение, а затем объединить результаты.
Это работает так, как написано, поскольку находится в LINQ to Objects. Если LINQ to SQL или другое, обработчик запросов может не поддерживать безопасную навигацию или другие операции. Вы должны использовать условный оператор для условного получения значений.
т.е.
источник
AsEnumerable()
перед выполнением объединения / объединения. Попробуйте и посмотрите, как это происходит. Если это не тот путь, по которому ты хочешь идти, я не уверен, что смогу помочь тебе больше.Обновление 1: предоставление действительно обобщенного метода расширения.
FullOuterJoin
Обновление 2: возможно принятие пользовательского параметра
IEqualityComparer
для типа ключа.Обновление 3 : эта реализация недавно стала частью
MoreLinq
- Спасибо, ребята!Редактировать Добавлено
FullOuterGroupJoin
( ideone ). Я повторно использовалGetOuter<>
реализацию, сделав ее менее производительной, чем могла бы быть, но сейчас я стремлюсь к «высокоуровневому» коду, а не к оптимизированной передовой технологии.Смотрите это в прямом эфире на http://ideone.com/O36nWc
Печатает вывод:
Вы также можете указать значения по умолчанию: http://ideone.com/kG4kqO
Печать:
Объяснение используемых терминов:
Присоединение - это термин, заимствованный из реляционной базы данных:
a
стольких раз , сколько есть элементыb
с соответствующим ключом (то есть: ничего , если быb
не было пусто). База данных жаргон называет этоinner (equi)join
.a
для которых нет соответствующего элемента не существует вb
. (то есть: даже результаты, еслиb
были пусты). Обычно это называетсяleft join
.a
, а такжеb
, если нет соответствующего элемента не существует в другом. (т.е. даже результаты, еслиa
были пусты)Что-то, чего обычно не наблюдается в СУБД, - это групповое объединение [1] :
a
для нескольких соответствующихb
, это группы записей с соответствующими ключами. Это часто более удобно, когда вы хотите перечислять через «объединенные» записи на основе общего ключа.Смотрите также GroupJoin, который также содержит некоторые общие объяснения.
[1] (Я считаю, что Oracle и MSSQL имеют собственные расширения для этого)
Полный код
Обобщенный класс расширения для этого
источник
FullOuterJoin
метода расширенияa.GroupBy(selectKeyA).ToDictionary();
какa.ToLookup(selectKeyA)
иadict.OuterGet(key)
какalookup[key]
. Получение коллекции ключей немного сложнее, хотя:alookup.Select(x => x.Keys)
.Я думаю, что с большинством из них есть проблемы, включая принятый ответ, потому что они плохо работают с Linq по сравнению с IQueryable либо из-за слишком большого количества обращений к серверу и слишком большого количества возвратов данных, либо из-за слишком большого количества выполнения клиента.
Для IEnumerable мне не нравится ответ Sehe или аналогичный, потому что он использует слишком много памяти (простой тест 10000000 с двумя списками вывел Linqpad из памяти на моей машине с 32 ГБ).
Кроме того, большинство других на самом деле не реализуют правильное полное внешнее соединение, потому что они используют объединение с правым соединением вместо Concat с правым анти-полусоединением, что не только удаляет дубликаты строк внутреннего соединения из результата, но и любые правильные дубликаты, которые изначально существовали в данных слева или справа.
Итак, вот мои расширения, которые решают все эти проблемы, генерируют SQL, а также реализуют соединение в LINQ to SQL напрямую, выполняются на сервере и работают быстрее и с меньшим объемом памяти, чем другие в Enumerables:
Разница между правильным анти-полусоединением в основном спорна с Linq to Objects или в исходном коде, но имеет значение на стороне сервера (SQL) в окончательном ответе, удаляя ненужное
JOIN
.Ручное кодирование
Expression
для обработки слиянияExpression<Func<>>
в лямбду может быть улучшено с помощью LinqKit, но было бы неплохо, если бы язык / компилятор добавил некоторую помощь для этого.FullOuterJoinDistinct
иRightOuterJoin
включены для полноты, но я еще не реализовалFullOuterGroupJoin
.я написал еще одну версию полного внешнего соединения для
IEnumerable
случаев, когда ключ можно заказать, что примерно на 50% быстрее, чем объединение левого внешнего соединения с правым антиполусоединением, по крайней мере, для небольших коллекций. Он проходит каждую коллекцию после сортировки только один раз.Я также добавил еще один ответ для версии, которая работает с EF, заменив
Invoke
пользовательское расширение.источник
TP unusedP, TC unusedC
? Они буквально не используются?TP
,TC
,TResult
чтобы создать правильныйExpression<Func<>>
. Я должен я мог заменить их_
,__
,___
вместо этого, но это не кажется более ясным , пока C # не имеет надлежащего подстановочные параметр , чтобы использовать вместо.The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
. Есть ли ограничения с этим кодом? Я хочу , чтобы выполнить полный РЕГИСТРИРУЙТЕСЬ над IQueryablesInvoke
пользовательский,ExpressionVisitor
чтобы встроить,Invoke
чтобы он работал с EF. Можешь попробовать?Вот метод расширения, который делает это:
источник
Union
удаляет дубликаты, поэтому, если в исходных данных есть повторяющиеся строки, они не будут в результате.Я предполагаю, что подход @ sehe сильнее, но пока я не понимаю его лучше, я обнаружил, что прыгаю от расширения @ MichaelSander. Я изменил его, чтобы он соответствовал синтаксису и типу возвращаемого значения встроенного метода Enumerable.Join (), описанного здесь . Я добавил «отличный» суффикс в отношении комментария @ cadrell0 к решению @ JeffMercado.
В этом примере вы бы использовали это так:
В будущем, когда я узнаю больше, у меня будет ощущение, что я перейду к логике @ sehe, учитывая ее популярность. Но даже тогда мне придется быть осторожным, потому что я считаю, что важно иметь хотя бы одну перегрузку, которая соответствует синтаксису существующего метода ".Join ()", если это возможно, по двум причинам:
Я все еще новичок в обобщениях, расширениях, заявлениях Func и других функциях, поэтому отзывы, безусловно, приветствуются.
РЕДАКТИРОВАТЬ: Мне не потребовалось много времени, чтобы понять, что была проблема с моим кодом. Я делал .Dump () в LINQPad и смотрел тип возвращаемого значения. Это было просто IEnumerable, поэтому я попытался сопоставить его. Но когда я на самом деле сделал .Where () или .Select () для моего расширения, я получил ошибку: «System Collections.IEnumerable» не содержит определения «Select» и ... ». В итоге я смог сопоставить входной синтаксис .Join (), но не поведение возврата.
РЕДАКТИРОВАТЬ: Добавлено «TResult» в тип возвращаемого значения для функции. Пропустил это при прочтении статьи Microsoft, и это конечно имеет смысл. С этим исправлением теперь кажется, что возвращаемое поведение соответствует моим целям.
источник
Как вы обнаружили, у Linq нет конструкции "внешнего соединения". Самое близкое, что вы можете получить - это левое внешнее объединение, используя указанный вами запрос. К этому вы можете добавить любые элементы списка фамилий, которые не представлены в объединении:
источник
Мне нравится ответ sehe, но он не использует отложенное выполнение (входные последовательности охотно перечисляются при вызовах ToLookup). Поэтому, посмотрев источники .NET для LINQ-to-objects , я пришел к следующему:
Эта реализация имеет следующие важные свойства:
Эти свойства важны, потому что они ожидают того, кто новичок в FullOuterJoin, но имеет опыт работы с LINQ.
источник
Я решил добавить это как отдельный ответ, поскольку я не уверен, что это достаточно проверено. Это повторная реализация
FullOuterJoin
метода с использованием, по сути, упрощенной, настраиваемой версииLINQKit
Invoke
/Expand
дляExpression
того, чтобы он работал в Entity Framework. Там не так много объяснений, как и мой предыдущий ответ.источник
base.Visit(node)
не должно выбрасывать исключение, поскольку это просто повторяется вниз по дереву. Я могу получить доступ практически к любой службе совместного использования кода, но не могу настроить тестовую базу данных. Тем не менее, выполнение этого теста на моем LINQ to SQL работает нормально.Guid
ключом иGuid?
внешним ключом?Выполняет потоковое перечисление в памяти для обоих входов и вызывает селектор для каждой строки. Если на текущей итерации корреляции нет, один из аргументов селектора будет нулевым .
Пример:
Требует IComparer для типа корреляции, использует Comparer.Default, если не указано.
Требует, чтобы 'OrderBy' применялся к входным перечислимым
источник
OrderBy
оба ключевых прогноза.OrderBy
буферизирует всю последовательность по очевидным причинам .Мое чистое решение для ситуации, когда ключ уникален в обоих перечислимых значениях:
так
выходы:
источник
Полное внешнее объединение для двух или более таблиц. Сначала извлеките столбец, к которому хотите присоединиться.
Затем используйте левое внешнее соединение между извлеченным столбцом и основными таблицами.
источник
Я написал этот класс расширений для приложения, возможно, 6 лет назад, и с тех пор использую его во многих решениях без проблем. Надеюсь, поможет.
редактировать: я заметил, что некоторые могут не знать, как использовать класс расширения.
Чтобы использовать этот класс расширения, просто обратитесь к его пространству имен в своем классе, добавив следующую строку, используя joinext;
^ это должно позволить вам видеть смысл функций расширения в любом наборе объектов IEnumerable, который вы случайно используете.
Надеюсь это поможет. Дайте мне знать, если это все еще не ясно, и я надеюсь, напишу пример того, как его использовать.
Теперь вот класс:
источник
SelectMany
не может быть преобразована в дерево выражений, достойное LINQ2SQL.Я думаю, что предложение LINQ join не является правильным решением этой проблемы, поскольку цель предложения join состоит не в том, чтобы накапливать данные таким образом, как это требуется для решения этой задачи. Код для объединения созданных отдельных коллекций становится слишком сложным, может быть, это нормально для целей обучения, но не для реальных приложений. Одним из способов решения этой проблемы является приведенный ниже код:
Если реальные коллекции велики для формирования HashSet, то вместо циклов foreach можно использовать код ниже:
источник
Спасибо всем за интересные посты!
Я изменил код, потому что в моем случае мне нужно было
Для интересующихся это мой модифицированный код (в VB, извините)
источник
Еще одно полное внешнее соединение
Так как не был доволен простотой и удобочитаемостью других предложений, я закончил с этим:
Он не имеет предварительного напряжения, чтобы быть быстрым (около 800 мс, чтобы присоединиться 1000 * 1000 на 2020 м CPU: 2,4 ГГц / 2 ядра). Для меня это просто компактное и повседневное полное внешнее соединение.
Он работает так же, как SQL FULL OUTER JOIN (сохранение дубликатов)
Ура ;-)
Идея состоит в том, чтобы
Вот краткий тест, который идет с этим:
Установите точку останова в конце, чтобы вручную убедиться, что она ведет себя так, как ожидается
}
источник
Я действительно ненавижу эти выражения linq, вот почему SQL существует:
Создайте это как представление sql в базе данных и импортируйте как сущность.
Конечно, (различное) объединение левого и правого объединений также сделает это, но это глупо.
источник