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

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

Klauzule join přebírá jako vstup dvě zdrojové sekvence. Prvky v každé sekvenci musí být nebo musí obsahovat vlastnost, která se dá porovnat s odpovídající vlastností v druhé sekvenci. Klauzule join porovnává zadané klíče pro rovnost pomocí speciálního equals klíčového slova. Všechna spojení prováděná join klauzulí jsou equijoins. Tvar výstupu join klauzule závisí na konkrétním typu spojení, které provádíte. Toto jsou 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í equijoin. 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 naleznete v tématu Provádění vnitřních spojení.

Připojení ke skupině

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

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 nejsou nalezeny žádné prvky ze správné zdrojové sekvence, které by odpovídaly prvku v levém zdroji, join klauzule vytvoří prázdné pole pro danou položku. Proto spojení skupiny je v podstatě vnitřní koňské spojení s tím rozdílem, že výsledná sekvence je uspořádaná 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 najdete v tématu Provádění seskupených spojení.

Levé vnější spojení

V levém vnějším spojení se 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 DefaultIfEmpty metodu v kombinaci se spojením skupiny k určení výchozího pravého prvku pro vytvoření, pokud levý prvek nemá žádné shody. Jako výchozí hodnotu můžete použít null libovolný typ odkazu nebo můžete zadat výchozí typ definovaný uživatelem. 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 tématu Provádění levých vnějších spojení.

Operátor rovná se

Klauzule A join provádí equijoin. Jinými slovy, můžete založit pouze shody na 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 == používá equals 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ů equals má přetížení pro porovnání podle hodnoty a operátor == používá rovnost odkazů. Pokud obě strany porovnání mají stejné řetězcové proměnné equals a == dosáhne 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 interning. 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ší: pomocí equals, levý klíč spotřebovává vnější zdrojovou sekvenci a pravý klíč spotřebovává vnitřní zdroj. Vnější zdroj je pouze v oboru na levé straně equals a vnitřní zdrojová sekvence je pouze v oboru na pravé straně.

Non-equijoins

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 from spojení. Další informace naleznete v tématu Provádění vlastních operací spojení.

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

Ve výrazu dotazu LINQ se operace spojení provádějí u kolekcí objektů. Kolekce objektů nelze "spojit" úplně stejným způsobem jako dvě relační tabulky. V LINQ jsou explicitní join klauzule vyžadovány pouze v případě, že dvě zdrojové sekvence nejsou svázané žádnou relací. Při práci s LINQ to SQL jsou tabulky cizích klíčů reprezentovány v objektovém modelu 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á třída Customer vlastnost Orders, která obsahuje kolekci Objednávek přidružených k danému zákazníkovi. Spojení už pro vás bylo provedeno.

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

Složené klávesy

Rovnost více hodnot můžete otestovat pomocí složeného klíče. Další informace naleznete v tématu Spojení pomocí složených klíčů. Složené klíče lze také použít 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: {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.
*/

Poznámky

join Klauzule, za kterou není následovatinto, se přeloží do Join volání metody. join Klauzule, za into kterou následuje, se přeloží na GroupJoin volání metody.

Viz také