Klauzula join (odwołanie w C#)
Klauzula jest przydatna join
do kojarzenia elementów z różnych sekwencji źródłowych, które nie mają bezpośredniej relacji w modelu obiektów. Jedynym wymaganiem jest, aby elementy w każdym źródle współużytkować pewną wartość, którą można porównać pod kątem równości. Na przykład dystrybutor żywności może mieć listę dostawców określonego produktu i listę nabywców. Można join
na przykład użyć klauzuli , aby utworzyć listę dostawców i nabywców tego produktu, którzy znajdują się w tym samym regionie.
Klauzula przyjmuje join
dwie sekwencje źródłowe jako dane wejściowe. Elementy w każdej sekwencji muszą być albo zawierać właściwość, którą można porównać z odpowiednią właściwością w innej sekwencji. Klauzula join
porównuje określone klucze pod kątem równości przy użyciu specjalnego equals
słowa kluczowego. Wszystkie sprzężenia wykonywane przez klauzulę join
są równoczesne. Kształt danych wyjściowych klauzuli join
zależy od określonego typu sprzężenia, które wykonujesz. Poniżej przedstawiono trzy najbardziej typowe typy sprzężenia:
Sprzężenie wewnętrzne
Dołącz do grupy
Lewe sprzężenia zewnętrzne
Sprzężenie wewnętrzne
W poniższym przykładzie pokazano prostą wewnętrzną równowznię. To zapytanie tworzy płaską sekwencję par "nazwa produktu/kategoria". Ten sam ciąg kategorii będzie wyświetlany w wielu elementach. Jeśli element z elementu nie categories
ma pasującego products
elementu , ta kategoria nie będzie wyświetlana w wynikach.
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
Aby uzyskać więcej informacji, zobacz Wykonywanie sprzężeń wewnętrznych.
Dołącz do grupy
Klauzula join
z wyrażeniem into
jest nazywana sprzężeniami grupowymi.
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 };
Sprzężenia grupy tworzy hierarchiczną sekwencję wyników, która kojarzy elementy w sekwencji źródła po lewej stronie z co najmniej jednym pasującym elementem w sekwencji źródła po prawej stronie. Sprzężenie grupy nie ma odpowiednika w kategoriach relacyjnych; zasadniczo jest to sekwencja tablic obiektów.
Jeśli nie znaleziono żadnych elementów z prawej sekwencji źródłowej pasujących do elementu w lewym źródle, klauzula join
spowoduje wygenerowanie pustej tablicy dla tego elementu. W związku z tym sprzężenie grupy jest nadal w zasadzie wewnętrzne równojoin, z tą różnicą, że sekwencja wyników jest zorganizowana w grupy.
Jeśli po prostu wybierzesz wyniki sprzężenia grupy, możesz uzyskać dostęp do elementów, ale nie możesz zidentyfikować klucza, do którego pasują. W związku z tym zazwyczaj bardziej przydatne jest wybranie wyników sprzężenia grupy w nowy typ, który ma również nazwę klucza, jak pokazano w poprzednim przykładzie.
Możesz również oczywiście użyć wyniku sprzężenia grupy jako generatora innego podzapytania:
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;
Aby uzyskać więcej informacji, zobacz Wykonywanie sprzężeń grupowanych.
Lewe sprzężenia zewnętrzne
W lewym sprzężeniu zewnętrznym zwracane są wszystkie elementy w sekwencji lewej źródła, nawet jeśli żadne pasujące elementy nie znajdują się w odpowiedniej sekwencji. Aby wykonać lewe sprzężenie zewnętrzne w LINQ, użyj DefaultIfEmpty
metody w połączeniu z sprzężenia grupy, aby określić domyślny element po prawej stronie do utworzenia, jeśli lewy element nie ma dopasowań. Można użyć null
jako wartości domyślnej dla dowolnego typu odwołania lub określić typ domyślny zdefiniowany przez użytkownika. W poniższym przykładzie pokazano domyślny typ zdefiniowany przez użytkownika:
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 };
Aby uzyskać więcej informacji, zobacz Wykonywanie sprzężeń zewnętrznych po lewej stronie.
Operator równości
Klauzula join
wykonuje równoczesne. Innymi słowy, można opierać tylko dopasowania na równości dwóch kluczy. Inne typy porównań, takie jak "większe niż" lub "nie równe", nie są obsługiwane. Aby wyjaśnić, że wszystkie sprzężenia są równoczesne, join
klauzula używa equals
słowa kluczowego ==
zamiast operatora . Słowo equals
kluczowe może być używane tylko w klauzuli join
i różni się od ==
operatora na kilka ważnych sposobów. Podczas porównywania ciągów ma przeciążenie do porównania według wartości, equals
a operator ==
używa równości odwołań. Gdy obie strony porównania mają identyczne zmienne equals
ciągu i ==
osiągną ten sam wynik: true. Dzieje się tak dlatego, że gdy program deklaruje co najmniej dwie równoważne zmienne ciągu, kompilator przechowuje wszystkie z nich w tej samej lokalizacji. Jest to nazywane interningowaniem. Kolejną ważną różnicą jest porównanie wartości null: null equals null
jest obliczana jako fałsz z operatorem equals
, a nie ==
operatorem, który ocenia ją jako true. Ponadto zachowanie określania zakresu jest inne: w przypadku equals
, lewy klucz zużywa zewnętrzną sekwencję źródłową, a prawy klucz zużywa źródło wewnętrzne. Źródło zewnętrzne znajduje się tylko w zakresie po lewej stronie, equals
a sekwencja źródła wewnętrznego znajduje się tylko w zakresie po prawej stronie.
Niezrównane
Można wykonywać operacje niezrównożne, sprzężenia krzyżowe i inne niestandardowe operacje sprzężenia, używając wielu from
klauzul, aby niezależnie wprowadzać nowe sekwencje do zapytania. Aby uzyskać więcej informacji, zobacz Wykonywanie niestandardowych operacji sprzężenia.
Sprzężenia w kolekcjach obiektów a tabele relacyjne
W wyrażeniu zapytania LINQ operacje sprzężenia są wykonywane w kolekcjach obiektów. Kolekcje obiektów nie mogą być "sprzężone" w dokładnie taki sam sposób jak dwie tabele relacyjne. W linQ klauzule jawne join
są wymagane tylko wtedy, gdy dwie sekwencje źródłowe nie są powiązane przez żadną relację. Podczas pracy z LINQ to SQL tabele kluczy obcych są reprezentowane w modelu obiektów jako właściwości tabeli podstawowej. Na przykład w bazie danych Northwind tabela Customer (Klient) ma relację klucza obcego z tabelą Orders (Zamówienia). Podczas mapowania tabel na model obiektów klasa Customer ma właściwość Orders zawierającą kolekcję Zamówień skojarzonych z tym klientem. W efekcie sprzężenia zostały już wykonane dla Ciebie.
Aby uzyskać więcej informacji na temat wykonywania zapytań dotyczących powiązanych tabel w kontekście LINQ to SQL, zobacz How to: Map Database Relationships (Instrukcje: mapowanie relacji bazy danych).
Klucze złożone
Aby sprawdzić równość wielu wartości, możesz użyć klucza złożonego. Aby uzyskać więcej informacji, zobacz Join by using composite keys (Łączenie przy użyciu kluczy złożonych). Klucze złożone mogą być również używane w klauzuli group
.
Przykład
Poniższy przykład porównuje wyniki sprzężenia wewnętrznego, sprzężenia grupy i lewego sprzężenia zewnętrznego w tych samych źródłach danych przy użyciu tych samych pasujących kluczy. Do tych przykładów dodawany jest dodatkowy kod, aby wyjaśnić wyniki na ekranie konsoli.
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: {0} items in 1 group.", innerJoinQuery.Count());
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: {0} items in {1} unnamed groups", totalItems, groupJoinQuery.Count());
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: {0} items in {1} named groups", totalItems, groupJoinQuery2.Count());
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(" {0}:{1}", item.ProductName, item.Category);
}
Console.WriteLine("GroupJoin3: {0} items in 1 group", totalItems);
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: {0} items in {1} groups", totalItems, leftOuterQuery.Count());
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: {0} items in 1 group", leftOuterQuery2.Count());
// 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: {0} items in 1 group", totalItems);
}
}
/*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.
*/
Uwagi
Klauzula join
, która nie jest po nim into
, jest tłumaczona na Join wywołanie metody. Klauzula join
, po której into
następuje, jest tłumaczona na GroupJoin wywołanie metody.
Zobacz też
- Słowa kluczowe zapytania (LINQ)
- Language Integrated Query (LINQ)
- Operacje połączone
- group, klauzula
- Wykonywanie lewych sprzężeń zewnętrznych
- Wykonywanie sprzężeń wewnętrznych
- Wykonywanie sprzężeń grupowanych
- Kolejność wyników klauzuli join
- Sprzęganie za pomocą kluczy złożonych
- Zgodne systemy baz danych dla programu Visual Studio