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 productselementu , 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ż