Partilhar via


Cláusula de junção (Referência C#)

Use a join cláusula para associar elementos de diferentes sequências de origem que não têm relação direta no modelo de objetos. O único requisito é que os elementos em cada fonte partilhem algum valor que possa comparar para igualdade. Por exemplo, um distribuidor alimentar pode ter uma lista de fornecedores de um determinado produto e uma lista de compradores. Pode usar uma join cláusula para criar uma lista dos fornecedores e compradores desse produto que estejam todos na mesma região especificada.

A referência da linguagem C# documenta a versão mais recentemente lançada da linguagem C#. Contém também documentação inicial para funcionalidades em versões preliminares públicas para a próxima versão da linguagem.

A documentação identifica qualquer funcionalidade introduzida pela primeira vez nas últimas três versões da língua ou em pré-visualizações públicas atuais.

Sugestão

Para saber quando uma funcionalidade foi introduzida pela primeira vez em C#, consulte o artigo sobre o histórico de versões da linguagem C#.

Uma cláusula join usa duas sequências de origem como entrada. Os elementos em cada sequência devem ser ou conter uma propriedade que possa comparar com uma propriedade correspondente na outra sequência. A join cláusula utiliza a palavra-chave especial equals para comparar as chaves especificadas para igualdade. Todas as junções que a join cláusula executa são equijoins. A forma da saída de uma join cláusula depende do tipo específico de junção que estás a realizar. A lista seguinte mostra os três tipos de junção mais comuns:

  • Junção interna
  • Adesão ao grupo
  • Junção externa esquerda

Junção interna

O exemplo a seguir mostra uma equijunção interna simples. Esta consulta produz uma sequência plana de pares "nome do produto / categoria". A mesma cadeia de categorias aparece em múltiplos elementos. Se um elemento de categories não tiver correspondência products, essa categoria não aparece nos resultados.

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

Para obter mais informações, consulte Executar junções internas.

Adesão ao grupo

Uma cláusula join com uma expressão into é chamada de associação de grupo.

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

Uma junção de grupo produz uma sequência de resultados hierárquica, que associa elementos na sequência de origem esquerda com um ou mais elementos correspondentes na sequência de origem do lado direito. Uma adesão de grupo não tem equivalente em termos relacionais; é essencialmente uma sequência de matrizes de objetos.

Se nenhum elemento da sequência da fonte direita corresponder a um elemento da fonte à esquerda, a join cláusula produz um array vazio para esse item. Portanto, a junção de grupos continua a ser basicamente uma equijunção interna, mas a diferença é que a sequência de resultados é organizada em grupos.

Se simplesmente selecionares os resultados de uma entrada em grupo, podes aceder aos itens, mas não consegues identificar a chave em que correspondem. Por isso, é geralmente mais útil selecionar os resultados da junção de grupo num novo tipo que também tenha o nome da chave, como mostrado no exemplo anterior.

Você pode também, é claro, usar o resultado de uma associação de grupo como gerador de outra subconsulta:

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;

Para obter mais informações, consulte Executar junções agrupadas.

Junção externa esquerda

Numa junção externa à esquerda, a consulta devolve todos os elementos da sequência fonte à esquerda, mesmo que nenhum elemento correspondente esteja na sequência direita. Para executar uma junção externa esquerda no LINQ, use o método DefaultIfEmpty em combinação com uma junção de grupo para especificar um elemento padrão do lado direito a ser produzido se um elemento do lado esquerdo não tiver correspondências. Você pode usar null como o valor padrão para qualquer tipo de referência ou pode especificar um tipo padrão definido pelo usuário. No exemplo a seguir, um tipo padrão definido pelo usuário é mostrado:

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

Para obter mais informações, consulte Executar junções externas à esquerda.

O operador de igualdade

Uma cláusula join executa uma equijunção. Em outras palavras, tu só podes basear as correspondências na igualdade entre duas chaves. Outros tipos de comparações, como "maior que" ou "não igual", não são suportados. Para deixar claro que todas as junções são equijoins, a cláusula join usa a palavra-chave equals em vez do operador ==. A palavra-chave equals só pode ser usada em uma cláusula join e difere do operador == em alguns aspetos importantes. Ao comparar cadeias de caracteres, equals tem uma sobrecarga para comparar por valor e o operador == usa a igualdade de referência. Quando ambos os lados da comparação têm variáveis de cadeia idênticas, equals e == chegam ao mesmo resultado: true. Isso porque, quando um programa declara duas ou mais variáveis de cadeia de caracteres equivalentes, o compilador armazena todas elas no mesmo local. Isso é conhecido como internamento . Outra diferença importante é a comparação com nulo: null equals null é avaliado como falso com o operador equals, em vez do operador == que o avalia como verdadeiro. Por fim, o comportamento de escopo é diferente: com equals, a chave esquerda consome a sequência de fonte externa, e a chave direita consome a fonte interna. A fonte externa está somente no âmbito do lado esquerdo do equals e a sequência da fonte interna está somente no âmbito do lado direito.

Não-equijunções

Você pode realizar junções não-equivalentes, junções cruzadas e outras operações personalizadas de junção usando várias cláusulas from para introduzir novas sequências independentemente numa consulta. Para obter mais informações, consulte Executar operações de junção personalizadas.

Junções em coleções de objetos versus tabelas relacionais

Numa expressão de consulta LINQ, realiza-se operações de junção em coleções de objetos. Não podes juntar coleções de objetos exatamente da mesma forma que duas tabelas relacionais. No LINQ, só precisas de orações explícitas join quando duas sequências de origem não têm qualquer relação. Quando se trabalha com LINQ para SQL, o modelo de objetos representa tabelas de chave estrangeira como propriedades da tabela primária. Por exemplo, no banco de dados Northwind, a tabela Customer tem uma relação de chave estrangeira com a tabela Orders. Quando mapeas as tabelas para o modelo de objetos, a classe Cliente tem uma Orders propriedade que contém a coleção de Orders associada a esse Cliente. Na prática, a junção já está feita para si.

Para obter mais informações sobre como consultar tabelas relacionadas no contexto do LINQ to SQL, consulte Como mapear relações de banco de dados.

Chaves compostas

Você pode testar a igualdade de vários valores usando uma chave composta. Para mais informações, consulte Join utilizando chaves compostas. Também podes usar chaves compostas numa group oração.

Exemplo

O exemplo a seguir compara os resultados de uma junção interna, uma junção de grupo e uma junção externa esquerda nas mesmas fontes de dados utilizando as mesmas chaves de correspondência. Algum código extra é adicionado a esses exemplos para esclarecer os resultados na exibição do console.

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

Observações

Uma join cláusula que não é seguida por into traduz-se numa Join chamada de método. Uma join cláusula seguida de into traduz-se numa GroupJoin chamada de método.

Ver também