Procedura: eseguire operazioni di join personalizzate (Guida per programmatori C#)

In questo esempio viene illustrato come eseguire operazioni di join che non sono possibili con la clausola join. In un'espressione di query, la clausola join è limitata e ottimizzata per gli equijoin, che sono in assoluto il tipo più comune di operazione di join. Quando si esegue un equijoin, è probabile che le prestazioni raggiungano sempre il livello ottimale utilizzando la clausola join.

Non è tuttavia possibile utilizzare la clausola join nei casi seguenti:

  • Quando il join viene affermato su un'espressione di ineguaglianza (un non equijoin).

  • Quando il join viene affermato su più di un'espressione di eguaglianza o di ineguaglianza.

  • Quando è necessario introdurre una variabile di intervallo temporanea per la sequenza del lato destro (interno) prima dell'operazione di join.

Per eseguire join che non sono equijoin, è possibile utilizzare più clausole from per introdurre ogni origine dati in modo indipendente. Si applica quindi un'espressione di predicato in una clausola where alla variabile di intervallo per ogni origine. L'espressione può inoltre avere il formato di una chiamata al metodo.

Nota

Non confondere questo tipo di operazione di join personalizzata con l'utilizzo di più clausole from per accedere a insiemi interni. Per ulteriori informazioni, vedere Clausola join (Riferimento C#).

Esempio

Nel primo metodo dell'esempio seguente viene illustrato un cross join semplice. I cross join devono essere utilizzati con attenzione perché possono produrre set di risultati molto grandi. Tuttavia, possono essere utili in alcuni scenari per la creazione di sequenze di origine a fronte delle quali vengono eseguite query aggiuntive.

Il secondo metodo produce una sequenza di tutti i prodotti il cui ID di categoria è presente nell'elenco di categorie sul lato sinistro. Si noti l'utilizzo della clausola let e del metodo Contains per creare una matrice temporanea. È inoltre possibile creare la matrice prima della query ed eliminare la prima clausola from.

     class CustomJoins
     {

         #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},         
 };

         // Specify the second data source.
         List<Product> products = new List<Product>()
{
   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},
   new Product{Name="Ice Cream", CategoryID=007},
   new Product{Name="Mackerel", CategoryID=012},
 };
         #endregion

         static void Main()
         {
             CustomJoins app = new CustomJoins();
             app.CrossJoin();
             app.NonEquijoin();

             Console.WriteLine("Press any key to exit.");
             Console.ReadKey();
         }

         void CrossJoin()
         {
             var crossJoinQuery =
                 from c in categories
                 from p in products
                 select new { c.ID, p.Name };

             Console.WriteLine("Cross Join Query:");
             foreach (var v in crossJoinQuery)
             {
                 Console.WriteLine("{0,-5}{1}", v.ID, v.Name);
             }
         }

         void NonEquijoin()
         {
             var nonEquijoinQuery =
                 from p in products
                 let catIds = from c in categories
                              select c.ID
                 where catIds.Contains(p.CategoryID) == true
                 select new { Product = p.Name, CategoryID = p.CategoryID };

             Console.WriteLine("Non-equijoin query:");
             foreach (var v in nonEquijoinQuery)
             {
                 Console.WriteLine("{0,-5}{1}", v.CategoryID, v.Product);
             }
         }
     }
     /* Output:
 Cross Join Query:
 1    Tea
 1    Mustard
 1    Pickles
 1    Carrots
 1    Bok Choy
 1    Peaches
 1    Melons
 1    Ice Cream
 1    Mackerel
 2    Tea
 2    Mustard
 2    Pickles
 2    Carrots
 2    Bok Choy
 2    Peaches
 2    Melons
 2    Ice Cream
 2    Mackerel
 3    Tea
 3    Mustard
 3    Pickles
 3    Carrots
 3    Bok Choy
 3    Peaches
 3    Melons
 3    Ice Cream
 3    Mackerel
 Non-equijoin query:
 1    Tea
 2    Mustard
 2    Pickles
 3    Carrots
 3    Bok Choy
 Press any key to exit.
      */

Nell'esempio seguente la query deve creare un join di due sequenze basate su chiavi corrispondenti che, nel caso della sequenza interna (lato destro), non possono essere ottenute prima della clausola join stessa. Qualora questo join venga eseguito con una clausola join, il metodo Split dovrà essere chiamato per ogni elemento. L'utilizzo di più clausole from consente alla query di evitare l'overhead della chiamata al metodo ripetuta. Tuttavia, poiché il join è ottimizzato, in questo caso particolare potrebbe risultare ancora più veloce rispetto all'utilizzo di più clausole from. I risultati varieranno principalmente in base al costo in termini di utilizzo della chiamata al metodo.

class MergeTwoCSVFiles
{
    static void Main()
    {
        // See section Compiling the Code for information about the data files.
        string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

        // Merge the data sources using a named type.
        // You could use var instead of an explicit type for the query.
        IEnumerable<Student> queryNamesScores =
            // Split each line in the data files into an array of strings.
            from name in names
            let x = name.Split(',')
            from score in scores
            let s = score.Split(',')
            // Look for matching IDs from the two data files.
            where x[2] == s[0]
            // If the IDs match, build a Student object.
            select new Student()
            {
                FirstName = x[0],
                LastName = x[1],
                ID = Convert.ToInt32(x[2]),
                ExamScores = (from scoreAsText in s.Skip(1)
                              select Convert.ToInt32(scoreAsText)).
                              ToList()
            };

        // Optional. Store the newly created student objects in memory
        // for faster access in future queries
        List<Student> students = queryNamesScores.ToList();

        foreach (var student in students)
        {
            Console.WriteLine("The average score of {0} {1} is {2}.",
                student.FirstName, student.LastName, student.ExamScores.Average());
        }

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

class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int ID { get; set; }
    public List<int> ExamScores { get; set; }
}

/* Output: 
    The average score of Omelchenko Svetlana is 82.5.
    The average score of O'Donnell Claire is 72.25.
    The average score of Mortensen Sven is 84.5.
    The average score of Garcia Cesar is 88.25.
    The average score of Garcia Debra is 67.
    The average score of Fakhouri Fadi is 92.25.
    The average score of Feng Hanying is 88.
    The average score of Garcia Hugo is 85.75.
    The average score of Tucker Lance is 81.75.
    The average score of Adams Terry is 85.25.
    The average score of Zabokritski Eugene is 83.
    The average score of Tucker Michael is 92.
 */

Compilazione del codice

  • Creare un progetto di applicazione console di Visual Studio destinato a .NET Framework versione 3.5 o successiva. Per impostazione predefinita, il progetto include un riferimento a System.Core.dll e una direttiva using per lo spazio dei nomi System.Linq.

  • Sostituire la classe Program con il codice dell'esempio precedente.

  • Seguire le istruzioni riportate in Procedura: unire contenuto da file dissimili (LINQ) per impostare i file di dati, scores.csv e names.csv.

  • Premere F5 per compilare ed eseguire il programma.

  • Premere un tasto per chiudere la finestra della console.

Vedere anche

Attività

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

Riferimenti

Clausola join (Riferimento C#)

Concetti

Espressioni di query LINQ (Guida per programmatori C#)

Operazioni di join

Cronologia delle modifiche

Data

Cronologia

Motivo

Agosto 2010

Modifica del secondo esempio, in modo che sia più semplice da eseguire.

Miglioramento delle informazioni.