Sdílet prostřednictvím


join – klauzule (Referenční dokumentace jazyka C#)

Pomocí klauzule přidružte join prvky z různých zdrojových sekvencí, které nemají v objektovém modelu žádný přímý vztah. Jediným požadavkem je, aby prvky v každém zdroji sdílely určitou hodnotu, kterou můžete porovnat s rovností. Například distributor potravin může mít seznam dodavatelů určitého výrobku a seznam kupujících. Klauzuli můžete použít join k vytvoření seznamu dodavatelů a kupujících daného produktu, kteří jsou všichni ve stejné zadané oblasti.

Referenční dokumentace jazyka C# dokumentuje naposledy vydané verze jazyka C#. Obsahuje také počáteční dokumentaci k funkcím ve verzi Public Preview pro nadcházející jazykovou verzi.

Dokumentace identifikuje všechny funkce, které byly poprvé představeny v posledních třech verzích jazyka nebo v aktuálních verzích Public Preview.

Návod

Informace o tom, kdy byla funkce poprvé představena v jazyce C#, najdete v článku o historii verzí jazyka C#.

Klauzule join přijímá jako vstup dvě zdrojové sekvence. Prvky v každé sekvenci musí být nebo musí obsahovat vlastnost, kterou můžete porovnat s odpovídající vlastností v druhé sekvenci. Klauzule join používá speciální equals klíčové slovo k porovnání zadaných klíčů pro rovnost. Všechna spojení, která klauzule provádí, join jsou equijoins. Tvar výstupu join klauzule závisí na konkrétním typu spojení, které provádíte. Následující seznam obsahuje tři nejběžnější typy spojení:

  • Vnitřní spojení
  • Připojení ke skupině
  • Levé vnější spojení

Vnitřní spojení

Následující příklad ukazuje jednoduchý vnitřní ekvijoin. Tento dotaz vytvoří plochou sekvenci párů "název produktu / kategorie". Stejný řetězec kategorie se zobrazí ve více prvech. Pokud prvek z categories nemá žádnou shodu products, tato kategorie se ve výsledcích nezobrazí.

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

Další informace viz Provádění vnitřních spojení.

Připojení ke skupině

Klauzule join s výrazem into se nazývá skupinové spojení.

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

Spojení skupiny vytvoří hierarchickou sekvenci výsledků, která přidruží prvky v levé zdrojové sekvenci k jednomu nebo více odpovídajícím prvkům v pravé sekvenci zdroje. Spojení skupiny nemá žádný ekvivalent v relačních termínech; je to v podstatě posloupnost polí objektů.

Pokud žádné prvky z správné zdrojové sekvence neodpovídají prvku v levém zdroji, join klauzule vytvoří prázdné pole pro danou položku. Tudíž je spojení skupin v podstatě vnitřní ekvijoin s tím rozdílem, že výsledek je uspořádán do skupin.

Pokud vyberete jenom výsledky připojení ke skupině, budete mít přístup k položkám, ale nemůžete identifikovat klíč, na který se shodují. Proto je obecně užitečnější vybrat výsledky spojení skupiny do nového typu, který má také název klíče, jak je znázorněno v předchozím příkladu.

Můžete také samozřejmě použít výsledek spojení skupiny jako generátor jiného poddotazu:

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;

Další informace naleznete v tématu Provádění seskupených spojení.

Levé vnější spojení

V levém vnějším spojení dotaz vrátí všechny prvky v levé zdrojové sekvenci, i když nejsou v správné sekvenci žádné odpovídající prvky. Chcete-li provést levé vnější spojení v LINQ, použijte metodu DefaultIfEmpty v kombinaci se skupinovým spojením pro určení výchozího prvku na pravé straně, který se vytvoří, pokud prvek na levé straně nemá žádné shody. Jako výchozí hodnotu pro libovolný typ odkazu můžete použít null, nebo můžete zadat uživatelsky definovaný výchozí typ. V následujícím příkladu je zobrazen výchozí typ definovaný uživatelem:

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

Další informace naleznete v Provedení levých vnějších spojení.

Operátor rovná se

Klauzule join provádí přirozené spojení. Jinými slovy, můžete porovnávat pouze na základě rovnosti dvou klíčů. Jiné typy porovnání, například "větší než" nebo "nerovná se", nejsou podporovány. Aby bylo jasné, že všechna spojení jsou equijoins, join klauzule místo operátoru equals používá == klíčové slovo. Klíčové equals slovo lze použít pouze v join klauzuli a liší se od operátoru == několika důležitými způsoby. Při porovnávání řetězců má equals přetížení pro porovnání podle hodnoty a operátor == používá kontrolu identity odkazů. Pokud obě strany porovnání mají identické řetězcové proměnné, equals a == dosáhnou stejného výsledku: true. Je to proto, že když program deklaruje dvě nebo více ekvivalentních řetězcových proměnných, kompilátor je uloží do stejného umístění. To se označuje jako internování. Dalším důležitým rozdílem je porovnání s hodnotou null: null equals null je vyhodnoceno jako nepravda s operátorem equals místo operátoru, == který ho vyhodnotí jako true. Nakonec se chování rozsahu liší: s equals, levé tlačítko využívá vnější zdrojovou sekvenci a pravé tlačítko využívá vnitřní zdrojovou sekvenci. Vnější zdroj je použitelný pouze na levé straně equals a vnitřní zdrojová sekvence je použitelná pouze na pravé straně.

Nerovná spojení

K nezávislému zavedení nových sekvencí do dotazu můžete provádět neekvižné operace, křížové spojení a další vlastní operace spojení pomocí několika klauzulí #D0. Další informace naleznete v části Provádění vlastních operací spojení.

Spojení v rámci kolekcí objektů vs. relačních tabulek

Ve výrazu dotazu LINQ provedete operace spojení u kolekcí objektů. Kolekce objektů nelze spojit úplně stejným způsobem jako dvě relační tabulky. V LINQ potřebujete explicitní join klauzule pouze v případě, že dvě zdrojové sekvence nemají žádnou relaci. Při práci s LINQ to SQL představuje objektový model tabulky cizích klíčů jako vlastnosti primární tabulky. Například v databázi Northwind má tabulka Customer relaci cizího klíče s tabulkou Orders. Při mapování tabulek na objektový model má Orders třída Customer vlastnost, která obsahuje kolekci přidruženou Orders k danému zákazníkovi. Spojení je už pro vás hotové.

Další informace o dotazování napříč souvisejícími tabulkami v kontextu LINQ to SQL naleznete v tématu Postupy: Mapování relací databáze.

Složené klávesy

Rovnost více hodnot můžete otestovat pomocí složeného klíče. Další informace naleznete v tématu Join pomocí složených klíčů. Složené klíče můžete použít také v klauzuli group .

Příklad

Následující příklad porovnává výsledky vnitřního spojení, spojení skupiny a levého vnějšího spojení ve stejných zdrojích dat pomocí stejných odpovídajících klíčů. Do těchto příkladů se přidá další kód, který objasní výsledky v zobrazení konzoly.

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: {innerJoinQuery.Count()} items in 1 group.");
        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: {totalItems} items in {groupJoinQuery.Count()} unnamed groups");
        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: {totalItems} items in {groupJoinQuery2.Count()} named groups");
        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($"   {item.ProductName}:{item.Category}");
        }

        Console.WriteLine($"GroupJoin3: {totalItems} items in 1 group");
        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: {totalItems} items in {leftOuterQuery.Count()} groups");
        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: {leftOuterQuery2.Count()} items in 1 group");
        // 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: {totalItems} items in 1 group");
    }
}
/*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.
*/

Poznámky

join Klauzule, za kterou následujeinto, se přeloží do Join volání metody. join Klauzule, která následujeinto, se přeloží na GroupJoin volání metody.

Viz také