Clausola join (Riferimento C#)

La clausola join è utile per l'associazione di elementi da sequenze di origine diverse senza relazione diretta nel modello a oggetti. Il requisito solo è che gli elementi in ogni origine condividano alcuni valori di cui può essere verificata l'uguaglianza. Un distributore di prodotti alimentari, ad esempio, potrebbe disporre di un elenco di fornitori di un determinato prodotto e di un elenco di acquirenti. Una clausola join consente, ad esempio, di creare un elenco dei fornitori e degli acquirenti di tale prodotto che si trovano tutti nell'area specificata.

La clausola join accetta due sequenze di origine come input. Gli elementi in ogni sequenza devono essere o contenere una proprietà che può essere confrontata con una proprietà corrispondente nell'altra sequenza. La clausola join verifica l'uguaglianza delle chiavi specificate utilizzando 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

Nell'esempio che segue viene fornito un inner equijoin. Questa query produce una semplice sequenza 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 ulteriori informazioni, vedere Procedura: eseguire degli inner join (Guida per programmatori C#).

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 produce una sequenza di risultati gerarchica, che associa gli elementi nella sequenza di origine a sinistra con uno o più elementi corrispondenti nella sequenza di origine sul lato destro. 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 produrrà una matrice vuota per tale elemento. Pertanto, il group join è ancora 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. È, pertanto, generalmente più utile selezionare i risultati del group join in un tipo nuovo che dispone anche del nome della chiave, come illustrato nell'esempio precedente.

È inoltre ovviamente possibile utilizzare 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 ulteriori informazioni, vedere Procedura: eseguire dei join raggruppati (Guida per programmatori C#).

Left outer join

In un left outer join, vengono restituiti tutti gli elementi nella sequenza di origine di sinistra, anche se non sono disponibili elementi corrispondenti nella sequenza di destra. Per eseguire un left outer join in LINQ, utilizzare il metodo DefaultIfEmpty in combinazione con un group join per specificare di produrre un elemento del lato destro predefinito se un elemento del lato sinistro non ha corrispondenze. È possibile utilizzare null come valore predefinito per qualsiasi tipo di riferimento o 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 ulteriori informazioni, vedere Procedura: eseguire dei left outer join (Guida per programmatori C#).

Operatore equals

Una clausola join esegue un equijoin. In altre parole, è possibile basare le corrispondenze solo sull'uguaglianza di due chiavi. Altri tipi di confronti, quale "greater than" o "not equals", non sono supportati. Per specificare chiaramente che tutti i join sono equijoin, la clausola join utilizza la parola chiave equals anziché l'operatore ==. La parola chiave equals può essere utilizzata solo in una clausola join e differisce dall'operatore == per un aspetto importante. 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 utilizzando più clausole from per introdurre indipendentemente nuove sequenze in una query. Per ulteriori informazioni, vedere la classe Procedura: eseguire operazioni di join personalizzate (Guida per programmatori C#).

Join su insiemi di oggetti etabelle relazionali

In un'espressione di query LINQ, le operazioni di join vengono eseguite su insiemi di oggetti. Gli insiemi di oggetti non possono essere "associati in join" con la stessa identica procedura utilizzata per due tabelle relazionali. In LINQ, le clausole join esplicite sono necessarie solo quando due sequenze di origine non sono associate da una relazione. Quando si utilizza LINQ to SQL, le tabelle di chiave esterna vengono rappresentate nel modello a oggetti come proprietà della tabella primaria. Nel database Northwind, ad esempio, la tabella Customer ha una relazione di chiave esterna con la tabella Orders. Quando si esegue il mapping delle tabelle al modello a oggetti, la classe Customer dispone di una proprietà Orders che contiene l'insieme di ordini associati a tale cliente. In effetti, il join è già stato eseguito automaticamente.

Per ulteriori informazioni sull'esecuzione di una query in tabelle correlate nel contesto di LINQ to SQL, vedere Procedura: mappare le relazioni di database (LINQ to SQL).

Chiavi composte

È possibile verificare l'uguaglianza di più valori utilizzando una chiave composta. Per ulteriori informazioni, vedere la classe Procedura: eseguire un join utilizzando una chiave composta (Guida per programmatori C#). Le chiavi composte possono essere utilizzate 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 utilizzando le stesse chiavi corrispondenti. A questi esempi viene aggiunto altro codice per chiarire i risultati nella visualizzazione della console.

    class JoinDemonstration
    {
        #region Data

        class Product
        {
            public string Name { get; set; }
            public int CategoryID { get; set; }
        }

        class Category
        {
            public string Name { get; set; }
            public int ID { get; set; }
        }

        // Specify the first data source.
        List<Category> categories = new List<Category>()
        { 
            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 List<Product>()
       {
          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();

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        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, groupJoinQuery3.Count());
            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:", prodGrouping.Count());
                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.
*/

Note

Una clausola join non seguita da into viene convertita in una chiamata al metodo Join. Una clausola join seguita da into viene convertita in una chiamata al metodo GroupJoin.

Vedere anche

Attività

Procedura: eseguire dei left outer join (Guida per programmatori C#)

Procedura: eseguire degli inner join (Guida per programmatori C#)

Procedura: eseguire dei join raggruppati (Guida per programmatori C#)

Procedura: ordinare i risultati di una clausola join (Guida per programmatori C#)

Procedura: eseguire un join utilizzando una chiave composta (Guida per programmatori C#)

Riferimenti

Clausola group (Riferimento C#)

Concetti

Espressioni di query LINQ (Guida per programmatori C#)

Operazioni di join

Altre risorse

Parole chiave di query (Riferimenti per C#)