Cláusula de junção (Referência C#)
A join
cláusula é útil para associar elementos de diferentes sequências de origem que não têm relação direta no modelo de objeto. O único requisito é que os elementos em cada fonte partilhem algum valor que possa ser comparado para a igualdade. Por exemplo, um distribuidor alimentar pode ter uma lista de fornecedores de um determinado produto e uma lista de compradores. Uma join
cláusula pode ser usada, por exemplo, para criar uma lista dos fornecedores e compradores desse produto que estão todos na mesma região especificada.
Uma join
cláusula usa duas sequências de origem como entrada. Os elementos em cada sequência devem ser ou conter uma propriedade que possa ser comparada a uma propriedade correspondente na outra sequência. A join
cláusula compara as chaves especificadas para igualdade usando a palavra-chave especial equals
. Todas as junções realizadas pela join
cláusula são equijunções. A forma da saída de uma join
cláusula depende do tipo específico de junção que você está executando. A seguir estão 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 caracteres de categoria aparecerá em vários elementos. Se um elemento de categories
não tiver correspondência products
, essa categoria não aparecerá 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 join
cláusula com uma into
expressão é 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 de origem direita for encontrado para corresponder a um elemento na fonte esquerda, a join
cláusula produzirá uma matriz vazia para esse item. Portanto, a junção do grupo ainda é basicamente uma equijunção interna, exceto que a sequência de resultados é organizada em grupos.
Se você apenas selecionar os resultados de uma associação de grupo, poderá acessar os itens, mas não poderá identificar a chave na qual eles correspondem. Portanto, geralmente é mais útil selecionar os resultados da associação de grupo em um novo tipo que também tenha o nome da chave, como mostrado no exemplo anterior.
Você também pode, é 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
Em uma junção externa esquerda, todos os elementos na sequência de origem esquerda são retornados, mesmo que nenhum elemento correspondente esteja na sequência direita. Para executar uma junção externa esquerda no LINQ, use o DefaultIfEmpty
método em combinação com uma associaçã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 igual
Uma join
cláusula executa uma equijunção. Em outras palavras, você só pode basear as correspondências na igualdade de 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 join
cláusula usa a equals
palavra-chave em vez do ==
operador. A equals
palavra-chave só pode ser usada em uma join
cláusula 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 igualdade de referência. Quando ambos os lados da comparação têm variáveis equals
de cadeia idênticas e ==
atingem o 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 estágio. Outra diferença importante é a comparação nula: null equals null
é avaliado como falso com equals
operador, em vez de ==
operador que o avalia como verdadeiro. Por fim, o comportamento de escopo é diferente: com , a equals
tecla esquerda consome a sequência de fonte externa, e a chave direita consome a fonte interna. A fonte externa está apenas no escopo no lado esquerdo e a sequência da equals
fonte interna está apenas no escopo no lado direito.
Não-equijunções
Você pode executar operações de junção não-equijoins, junções cruzadas e outras operações de junção personalizadas usando várias from
cláusulas para introduzir novas sequências independentemente em uma consulta. Para obter mais informações, consulte Executar operações de junção personalizadas.
Juntas em coleções de objetos vs. tabelas relacionais
Em uma expressão de consulta LINQ, as operações de junção são executadas em coleções de objetos. As coleções de objetos não podem ser "unidas" exatamente da mesma maneira que duas tabelas relacionais. No LINQ, cláusulas explícitas join
só são necessárias quando duas sequências de origem não estão vinculadas por nenhuma relação. Ao trabalhar com LINQ to SQL, tabelas de chave estrangeira são representadas no modelo de objeto 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 você mapeia as tabelas para o modelo de objeto, a classe Customer tem uma propriedade Orders que contém a coleção de Orders associada a esse Customer. Na verdade, a adesão já foi feita para você.
Para obter mais informações sobre consultas entre 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 obter mais informações, consulte Ingressar usando chaves compostas. Chaves compostas também podem ser usadas em uma group
cláusula.
Exemplo
O exemplo a seguir compara os resultados de uma associação interna, uma associação de grupo e uma associação externa esquerda nas mesmas fontes de dados usando as mesmas chaves correspondentes. 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: {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.
*/
Observações
Uma join
cláusula que não é seguida por into
é convertida em uma chamada de Join método. Uma join
cláusula seguida por é convertida em into
uma chamada de GroupJoin método.
Consulte também
- Palavras-chave de consulta (LINQ)
- Consulta integrada de linguagem (LINQ)
- Junte-se às operações
- Cláusula de grupo
- Executar junções externas à esquerda
- Executar junções internas
- Executar junções agrupadas
- Ordenar os resultados de uma cláusula de adesão
- Ingressar usando chaves compostas
- Sistemas de banco de dados compatíveis para Visual Studio