Gewusst wie: Ausführen von benutzerdefinierten Verknüpfungsoperationen (C#-Programmierhandbuch)

Aktualisiert: August 2010

In diesem Beispiel wird gezeigt, wie Sie Joinoperationen, die mit der join-Klausel nicht möglich sind, ausführen können. In einem Abfrageausdruck ist die join-Klausel auf Equijoins beschränkt und wurde für sie optimiert. Equijoins sind der häufigste Typ an Joinoperationen. Wenn Sie einen Equijoin ausführen, erhalten Sie mit der join-Klausel wahrscheinlich immer die optimale Leistung.

Die join-Klausel kann jedoch in den folgenden Fällen nicht verwendet werden:

  • Wenn der Join auf einem Ausdruck der Ungleichheit (ein Nicht-Equijoin) basiert wird.

  • Wenn der Join auf mehr als einem Ausdruck der Gleichheit oder Ungleichheit basiert wird.

  • Wenn Sie eine temporäre Bereichsvariable für die rechte (innere) Sequenz vor der Joinoperation verwenden müssen.

Um Joins auszuführen, bei denen es sich nicht um Equijoins handelt, können Sie mehrere from-Klauseln verwenden, um jede Datenquelle einzeln einzuführen. Sie wenden dann einen Prädikatsausdruck in einer where-Klausel auf die Bereichsvariable für jede Quelle an. Der Ausdruck kann auch die Form eines Methodenaufrufs annehmen.

Tipp

Verwechseln Sie diese Art von benutzerdefinierten Joinoperationen nicht mit dem Einsatz von mehreren from-Klauseln, um auf innere Sammlungen zuzugreifen. Weitere Informationen finden Sie unter join-Klausel (C#-Referenz).

Beispiel

Die erste Methode im folgenden Beispiel zeigt einen einfachen Cross Join. Cross Joins müssen mit Vorsicht verwendet werden, da sie sehr große Ergebnissätze erzeugen können. Sie können jedoch in einigen Szenarien zum Erstellen von Quellsequenzen, gegen die zusätzliche Abfragen ausgeführt werden, nützlich sein.

Die zweite Methode erzeugt eine Sequenz aller Produkte, deren Kategorie-ID in der Kategorieliste links aufgeführt ist. Beachten Sie die Verwendung der let-Klausel und der Contains-Methode, um ein temporäres Array zu erstellen. Sie können das Array auch vor der Abfrage erstellen und die erste from-Klausel ausschließen.

     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.
      */

Im folgenden Beispiel muss die Abfrage zwei Sequenzen anhand übereinstimmender Schlüssel verknüpfen, die im Fall der inneren (rechten) Sequenz nicht vor der join-Klausel selbst abgerufen werden können. Wenn dieser Join mit einer join-Klausel ausgeführt wird, muss die Split-Methode für jedes Element aufgerufen werden. Der Einsatz von mehreren from-Klauseln ermöglicht es der Abfrage, den Aufwand eines wiederholten Methodenaufrufs zu verhindern. Da der join optimiert ist, ist dies in diesem speziellen Fall vermutlich dennoch schneller als die Verwendung mehrerer from-Klauseln. Die Ergebnisse ändern sich hauptsächlich in Abhängigkeit davon, wie teuer der Methodenaufruf ist.

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.
 */

Kompilieren des Codes

  • Erstellen Sie ein Visual Studio-Konsolenanwendungsprojekt für .NET Framework 3.5 oder höher. Standardmäßig weist das Projekt einen Verweis auf System.Core.dll und eine using-Direktive für den System.Linq-Namespace auf.

  • Ersetzen Sie die Program-Klasse durch den Code im vorherigen Beispiel.

  • Befolgen Sie die Anweisungen in Gewusst wie: Verknüpfen des Inhalts unterschiedlicher Dateien (LINQ), um die Datendateien scores.csv und names.csv einzurichten.

  • Drücken Sie F5, um das Programm zu kompilieren und auszuführen.

  • Drücken Sie eine beliebige Taste, um das Konsolenfenster zu schließen.

Siehe auch

Aufgaben

Gewusst wie: Sortieren der Ergebnisse einer Join-Klausel (C#-Programmierhandbuch)

Referenz

join-Klausel (C#-Referenz)

Konzepte

LINQ-Abfrageausdrücke (C#-Programmierhandbuch)

Verknüpfungsvorgänge

Änderungsprotokoll

Datum

Versionsgeschichte

Grund

August 2010

Das zweite Beispiel wurde geändert, damit es einfacher ausgeführt werden kann.

Informationsergänzung.