Clausola join (Riferimento C#)
Il clausola join
è utile per l'associazione di elementi di sequenze di origine diverse che non hanno una relazione diretta nel modello a oggetti. L'unico requisito è che gli elementi in ogni origine condividano alcuni valori di cui può essere verificata l'uguaglianza. Un distributore di prodotti alimentari, ad esempio, potrebbe avere un elenco di fornitori di un determinato prodotto e un elenco di acquirenti. Con una clausola join
è ad esempio possibile creare un elenco dei fornitori e degli acquirenti di tale prodotto che si trovano tutti nella stessa area specificata.
Una clausola join
accetta due sequenze di origine come input. Gli elementi in ogni sequenza devono essere o devono contenere una proprietà che può essere confrontata con una proprietà corrispondente nell'altra sequenza. La clausola join
verifica l'uguaglianza delle chiavi specificate usando la speciale parola chiave equals
. Tutti i join eseguiti dalla clausola join
sono equijoin. La forma dell'output di una clausola join
dipende dal tipo specifico di join che si sta eseguendo. Di seguito vengono riportati i tre tipi di join più comuni:
Inner join
Group join
Left outer join
Inner join
In questo esempio viene illustrato un semplice inner equijoin. Questa query genera una sequenza semplice di coppie "nome prodotto / categoria". La stessa stringa della categoria verrà visualizzata in più elementi. Se per un elemento di categories
non esistono products
corrispondenti, tale categoria non verrà visualizzata nei risultati.
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
Per altre informazioni, vedere Eseguire inner join.
Group join
Una clausola join
con un'espressione into
viene detta group join.
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 };
Un group join genera una sequenza di risultati gerarchici, che associa gli elementi nella sequenza di origine a sinistra con uno o più elementi corrispondenti nella sequenza di origine a destra. Un group join non ha equivalente in termini relazionali. Si tratta essenzialmente di una sequenza di matrici di oggetti.
Se nessun elemento della sequenza di origine a destra corrisponde a un elemento nell'origine a sinistra, la clausola join
genererà una matrice vuota per tale elemento. Il group join rimane fondamentalmente un inner equijoin tranne per il fatto che la sequenza di risultati è organizzata in gruppi.
Se si selezionano i risultati di un group join, è possibile accedere agli elementi, ma non è possibile identificare la chiave alla quale corrispondono. È quindi generalmente più utile selezionare i risultati del group join in un tipo nuovo che ha anche il nome della chiave, come illustrato nell'esempio precedente.
È ovviamente anche possibile usare il risultato di un group join come generatore di un'altra sottoquery:
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;
Per altre informazioni, vedere Eseguire join raggruppati.
Left outer join
In un left outer join vengono restituiti tutti gli elementi nella sequenza di origine a sinistra, anche se non sono disponibili elementi corrispondenti nella sequenza a destra. Per eseguire un left outer join in LINQ, usare il metodo DefaultIfEmpty
insieme a un group join per specificare di generare un elemento di destra predefinito se un elemento di sinistra non ha corrispondenze. È possibile usare null
come valore predefinito per qualsiasi tipo di riferimento oppure specificare un tipo predefinito definito dall'utente. Nell'esempio seguente viene illustrato un tipo predefinito definito dall'utente:
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 };
Per altre informazioni, vedere Eseguire left outer join.
Operatore di uguaglianza
Una clausola join
esegue un equijoin. In altre parole, è possibile basare le corrispondenze solo sull'uguaglianza di due chiavi. Altri tipi di confronti, quale "maggiore di" o "diverso da", non sono supportati. Per specificare chiaramente che tutti i join sono equijoin, la clausola join
usa la parola chiave equals
anziché l'operatore ==
. La parola chiave equals
può essere usata solo in una clausola join
e differisce dall'operatore ==
per aspetti importanti. Durante il confronto delle stringhe, equals
ha un overload da confrontare per valore e l'operatore ==
usa l'uguaglianza dei riferimenti. Quando entrambi i lati del confronto hanno variabili stringa identiche, equals
e ==
raggiungono lo stesso risultato: true. Questo perché, quando un programma dichiara due o più variabili di stringa equivalenti, il compilatore li archivia tutti nella stessa posizione. Questa operazione è nota come centralizzazione. Un'altra differenza importante è il confronto tra valori null: null equals null
viene valutato come false con l'operatore equals
, anziché l'operatore ==
che lo valuta come true. Infine, il comportamento di ambito è diverso: con equals
, la chiave sinistra utilizza la sequenza di origine esterna e la chiave destra utilizza l'origine interna. L'origine esterna si trova solo nell'ambito sul lato sinistro di equals
e la sequenza di origine interna si trova solo nell'ambito sul lato destro.
Non equijoin
È possibile eseguire non equijoin, cross join e altre operazioni di join personalizzate usando più clausole from
per introdurre indipendentemente nuove sequenze in una query. Per altre informazioni, vedere Eseguire operazioni di join personalizzate.
Join su raccolte di oggetti e tabelle relazionali
In un'espressione di query LINQ le operazioni di join vengono eseguite su raccolte di oggetti. Le raccolte di oggetti non possono essere "aggiunte" nello stesso modo in cui si aggiungono due tabelle relazionali. In LINQ le clausole join
esplicite sono necessarie solo quando due sequenze di origine non sono legate da una relazione. Quando si usa LINQ to SQL, le tabelle di chiavi esterne vengono rappresentate nel modello a oggetti come proprietà della tabella primaria. Nel database Northwind, ad esempio, la tabella Customer ha una relazione di chiavi esterne con la tabella Orders. Quando si esegue il mapping delle tabelle al modello a oggetti, la classe Customer presenta una proprietà Orders che contiene la raccolta degli ordini associati a tale cliente. In effetti, il join è già stato automaticamente eseguito.
Per altre informazioni sull'esecuzione di una query in tabelle correlate nel contesto di LINQ to SQL, vedere Procedura: Eseguire il mapping delle relazioni di database.
Chiavi composte
È possibile verificare l'uguaglianza di più valori usando una chiave composta. Per altre informazioni, vedere Eseguire un join usando chiavi composte. Le chiavi composte possono essere usate anche nella clausola group
.
Esempio
Nell'esempio seguente vengono confrontati i risultati di un inner join, di un group join e di un left outer join nelle stesse origini dati usando le medesime chiavi corrispondenti. A questi esempi viene aggiunto altro codice per chiarire i risultati nella visualizzazione della console.
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.
*/
Osservazioni:
Una clausola join
non seguita da into
viene traslata in una chiamata al metodo Join. Una clausola join
seguita da into
viene traslata in una chiamata al metodo GroupJoin.
Vedi anche
- Parole chiave di query (LINQ)
- LINQ (Language-Integrated Query)
- Operazioni di join
- Clausola group
- Eseguire left outer join
- Eseguire inner join
- Eseguire join raggruppati
- Ordinare i risultati di una clausola join
- Eseguire un join usando una chiave composta
- Sistemi di database compatibili per Visual Studio