Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Предложение join
полезно для связывания элементов из разных исходных последовательностей, которые не имеют прямой связи в объектной модели. Единственное требование заключается в том, что элементы в каждом источнике имеют некоторое значение, которое можно сравнить для равенства. Например, у распространителя продуктов питания может быть список поставщиков определенного продукта, а также список покупателей. Предложение join
можно использовать, например, для создания списка поставщиков и покупателей этого продукта, которые находятся в одном регионе.
Предложение join
принимает две исходные последовательности в качестве входных данных. Элементы в каждой последовательности должны быть или содержать свойство, которое можно сравнить с соответствующим свойством в другой последовательности. Условие join
сравнивает указанные ключи на предмет равенства с помощью специального ключевого слова equals
. Все соединения, выполненные предложением join
, являются эквивалентными. Структура выходных данных join
предложения зависит от конкретного типа выполняемого соединения. Ниже приведены три наиболее распространенных типа соединения:
Внутреннее соединение
Присоединение к группе
Левое внешнее соединение
Внутреннее соединение
В следующем примере показано простое внутреннее эквисоединение. Этот запрос создает плоскую последовательность пар "имя продукта / категория". Одна строка категории будет отображаться в нескольких элементах. Если элемент из categories
не соответствует products
, эта категория не будет отображаться в результатах.
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence
Дополнительные сведения см. в разделе "Выполнение внутренних соединений".
Присоединение к группе
Предложение join
с into
выражением называется групповым объединением.
var innerGroupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new { CategoryName = category.Name, Products = prodGroup };
Соединение группы создает иерархическую последовательность результатов, которая связывает элементы в левой исходной последовательности с одним или несколькими соответствующими элементами в правой исходной последовательности. Соединение группы не имеет эквивалента в реляционных терминах; это по сути последовательность массивов объектов.
Если элементы из правой исходной последовательности не найдены для сопоставления элемента в левом источнике, join
предложение создаст пустой массив для этого элемента. Таким образом, соединение группы по-прежнему является внутренним эквивалентным соединением, за исключением того, что последовательность результатов организована в группы.
Если вы просто выберете результаты группового соединения, вы можете получить доступ к элементам, но не можете определить ключ, на котором они основаны. Поэтому, как правило, более полезно выбрать результаты операции группового объединения в новый тип, который также имеет имя ключа, как показано в предыдущем примере.
Вы также можете использовать результат объединения группы в качестве генератора другого подзапроса:
var innerGroupJoinQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from prod2 in prodGroup
where prod2.UnitPrice > 2.50M
select prod2;
Дополнительные сведения см. в разделе "Выполнение группированных соединений".
Левое внешнее соединение
В левом внешнем соединении возвращаются все элементы в левой исходной последовательности, даже если соответствующие элементы не находятся в правой последовательности. Чтобы выполнить левое внешнее соединение в LINQ, используйте метод DefaultIfEmpty
в сочетании с групповым соединением, чтобы указать элемент справа по умолчанию, формируемый в случае, если для левого элемента не найдены совпадения. Можно использовать null
в качестве значения по умолчанию для любого ссылочного типа или указать определяемый пользователем тип по умолчанию. В следующем примере показан определяемый пользователем тип по умолчанию:
var leftOuterJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty(new Product { Name = String.Empty, CategoryID = 0 })
select new { CatName = category.Name, ProdName = item.Name };
Дополнительные сведения см. в разделе "Выполнение левых внешних соединений".
Оператор равенства
Предложение join
выполняет эквисоединение. Другими словами, можно основывать совпадения только на равенстве двух ключей. Другие типы сравнения, такие как "больше" или "не равно", не поддерживаются. Чтобы прояснить, что все соединения являются эквисоединениями, join
предложение использует equals
ключевое слово вместо ==
оператора. Ключевое equals
слово может использоваться только в join
предложении, и оно отличается от ==
оператора некоторыми важными способами. При сравнении строк equals
имеет перегрузку для сравнения по значению, а оператор ==
использует сравнение ссылок. Если обе стороны сравнения имеют одинаковые строковые переменные и equals
==
достигают одного результата: true. Это связано с тем, что, когда программа объявляет две или более эквивалентные строковые переменные, компилятор сохраняет все из них в одном расположении. Это называется интернированием. Еще одно важное различие заключается в сравнении null: null equals null
вычисляется как false с equals
оператором, а не ==
оператором, который вычисляет его как true. Наконец, поведение области отличается: при использовании equals
левый ключ обрабатывает внешнюю последовательность источника, а правый ключ обрабатывает внутренний источник. Внешний источник действует только в левой части equals
, а внутренняя последовательность — только в правой.
неравные соединения
Вы можете выполнять несовпадающие соединения, перекрестные соединения и другие пользовательские операции соединения с помощью нескольких from
предложений, чтобы независимо вводить новые последовательности в запрос. Дополнительные сведения см. в разделе "Выполнение пользовательских операций соединения".
Объединения в коллекциях объектов и реляционных таблицах
В выражении запроса LINQ операции соединения выполняются в коллекциях объектов. Коллекции объектов не могут быть "присоединены" точно так же, как и две реляционные таблицы. В LINQ явные join
предложения требуются только в том случае, если две исходные последовательности не связаны ни одной связью. При работе с LINQ to SQL внешние таблицы ключей представлены в объектной модели как свойства первичной таблицы. Например, в базе данных Northwind у таблицы Customer есть связь внешнего ключа с таблицей Orders. При сопоставлении таблиц с объектной моделью класс Customer имеет свойство Orders, содержащее коллекцию Orders, связанную с этим клиентом. Фактически, соединение уже выполнено для вас.
Дополнительные сведения о запросах между связанными таблицами в контексте LINQ to SQL см. в разделе "Практическое руководство. Сопоставление связей базы данных".
Составные ключи
Вы можете проверить равенство нескольких значений с помощью составного ключа. Дополнительные сведения см. в разделе "Присоединение с помощью составных ключей". Составные ключи также можно использовать в предложении group
.
Пример
В следующем примере сравниваются результаты внутреннего соединения, объединения группы и левого внешнего соединения в одних и том же источниках данных с использованием одинаковых ключей сопоставления. В эти примеры добавляется дополнительный код, чтобы уточнить результаты в отображении консоли.
class JoinDemonstration
{
#region Data
class Product
{
public required string Name { get; init; }
public required int CategoryID { get; init; }
}
class Category
{
public required string Name { get; init; }
public required int ID { get; init; }
}
// Specify the first data source.
List<Category> categories =
[
new Category {Name="Beverages", ID=001},
new Category {Name="Condiments", ID=002},
new Category {Name="Vegetables", ID=003},
new Category {Name="Grains", ID=004},
new Category {Name="Fruit", ID=005}
];
// Specify the second data source.
List<Product> products =
[
new Product {Name="Cola", CategoryID=001},
new Product {Name="Tea", CategoryID=001},
new Product {Name="Mustard", CategoryID=002},
new Product {Name="Pickles", CategoryID=002},
new Product {Name="Carrots", CategoryID=003},
new Product {Name="Bok Choy", CategoryID=003},
new Product {Name="Peaches", CategoryID=005},
new Product {Name="Melons", CategoryID=005},
];
#endregion
static void Main(string[] args)
{
JoinDemonstration app = new JoinDemonstration();
app.InnerJoin();
app.GroupJoin();
app.GroupInnerJoin();
app.GroupJoin3();
app.LeftOuterJoin();
app.LeftOuterJoin2();
}
void InnerJoin()
{
// Create the query that selects
// a property from each element.
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { Category = category.ID, Product = prod.Name };
Console.WriteLine("InnerJoin:");
// Execute the query. Access results
// with a simple foreach statement.
foreach (var item in innerJoinQuery)
{
Console.WriteLine("{0,-10}{1}", item.Product, item.Category);
}
Console.WriteLine($"InnerJoin: {innerJoinQuery.Count()} items in 1 group.");
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin()
{
// This is a demonstration query to show the output
// of a "raw" group join. A more typical group join
// is shown in the GroupInnerJoin method.
var groupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup;
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Simple GroupJoin:");
// A nested foreach statement is required to access group items.
foreach (var prodGrouping in groupJoinQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine($"Unshaped GroupJoin: {totalItems} items in {groupJoinQuery.Count()} unnamed groups");
Console.WriteLine(System.Environment.NewLine);
}
void GroupInnerJoin()
{
var groupJoinQuery2 =
from category in categories
orderby category.ID
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new
{
Category = category.Name,
Products = from prod2 in prodGroup
orderby prod2.Name
select prod2
};
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupInnerJoin:");
foreach (var productGroup in groupJoinQuery2)
{
Console.WriteLine(productGroup.Category);
foreach (var prodItem in productGroup.Products)
{
totalItems++;
Console.WriteLine(" {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
}
}
Console.WriteLine($"GroupInnerJoin: {totalItems} items in {groupJoinQuery2.Count()} named groups");
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin3()
{
var groupJoinQuery3 =
from category in categories
join product in products on category.ID equals product.CategoryID into prodGroup
from prod in prodGroup
orderby prod.CategoryID
select new { Category = prod.CategoryID, ProductName = prod.Name };
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupJoin3:");
foreach (var item in groupJoinQuery3)
{
totalItems++;
Console.WriteLine($" {item.ProductName}:{item.Category}");
}
Console.WriteLine($"GroupJoin3: {totalItems} items in 1 group");
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin()
{
// Create the query.
var leftOuterQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup.DefaultIfEmpty(new Product() { Name = "Nothing!", CategoryID = category.ID });
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Left Outer Join:");
// A nested foreach statement is required to access group items
foreach (var prodGrouping in leftOuterQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine($"LeftOuterJoin: {totalItems} items in {leftOuterQuery.Count()} groups");
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin2()
{
// Create the query.
var leftOuterQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty()
select new { Name = item == null ? "Nothing!" : item.Name, CategoryID = category.ID };
Console.WriteLine($"LeftOuterJoin2: {leftOuterQuery2.Count()} items in 1 group");
// Store the count of total items
int totalItems = 0;
Console.WriteLine("Left Outer Join 2:");
// Groups have been flattened.
foreach (var item in leftOuterQuery2)
{
totalItems++;
Console.WriteLine("{0,-10}{1}", item.Name, item.CategoryID);
}
Console.WriteLine($"LeftOuterJoin2: {totalItems} items in 1 group");
}
}
/*Output:
InnerJoin:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Peaches 5
Melons 5
InnerJoin: 8 items in 1 group.
Unshaped GroupJoin:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Group:
Peaches 5
Melons 5
Unshaped GroupJoin: 8 items in 5 unnamed groups
GroupInnerJoin:
Beverages
Cola 1
Tea 1
Condiments
Mustard 2
Pickles 2
Vegetables
Bok Choy 3
Carrots 3
Grains
Fruit
Melons 5
Peaches 5
GroupInnerJoin: 8 items in 5 named groups
GroupJoin3:
Cola:1
Tea:1
Mustard:2
Pickles:2
Carrots:3
Bok Choy:3
Peaches:5
Melons:5
GroupJoin3: 8 items in 1 group
Left Outer Join:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Nothing! 4
Group:
Peaches 5
Melons 5
LeftOuterJoin: 9 items in 5 groups
LeftOuterJoin2: 9 items in 1 group
Left Outer Join 2:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Nothing! 4
Peaches 5
Melons 5
LeftOuterJoin2: 9 items in 1 group
Press any key to exit.
*/
Замечания
Условие join
без следующего into
преобразуется в вызов метода Join. Предложение join
, за которым следует into
, преобразуется в GroupJoin вызов метода.
См. также
- Ключевые слова запроса (LINQ)
- Интегрированный язык запросов (LINQ)
- Операции присоединения
- групповая оговорка
- Выполнить левое внешнее соединение
- Выполняйте внутренние соединения
- Выполнение группированных соединений
- Упорядочить результаты оператора JOIN
- Присоединение с помощью составных ключей
- Совместимые системы баз данных для Visual Studio