Partilhar via


LINQ: Consulta de Language-Integrated do .NET

 

Don Box, Anders Hejlsberg

Fevereiro de 2007

Aplica-se a:
   Visual Studio Code nome "Orcas"
   .Net Framework 3.5

Resumo: As instalações de consulta de uso geral adicionadas à .NET Framework se aplicam a todas as fontes de informações, não apenas a dados relacionais ou XML. Essa instalação é chamada linq (consulta de Language-Integrated do .NET). (32 páginas impressas)

Sumário

Consulta de Language-Integrated do .NET
Introdução com operadores de consulta padrão
Recursos de linguagem que dão suporte ao projeto LINQ
Mais operadores de consulta padrão
Sintaxe da consulta
LINQ to SQL: Integração do SQL
LINQ to XML: Integração XML
Resumo

Consulta de Language-Integrated do .NET

Após duas décadas, a indústria atingiu um ponto estável na evolução das tecnologias de programação OO (orientadas a objetos). Os programadores agora tomam para recursos concedidos, como classes, objetos e métodos. Ao analisar a atual e a próxima geração de tecnologias, tornou-se evidente que o próximo grande desafio na tecnologia de programação é reduzir a complexidade de acessar e integrar informações que não são definidas nativamente usando a tecnologia de OO. As duas fontes mais comuns de informações não OO são bancos de dados relacionais e XML.

Em vez de adicionar recursos relacionais ou específicos de XML às nossas linguagens de programação e runtime, com o projeto LINQ, adotamos uma abordagem mais geral e estamos adicionando instalações de consulta de uso geral aos .NET Framework que se aplicam a todas as fontes de informações, não apenas a dados relacionais ou XML. Essa instalação é chamada linq (consulta de Language-Integrated do .NET).

Usamos o termo consulta integrada à linguagem para indicar que a consulta é um recurso integrado das principais linguagens de programação do desenvolvedor (por exemplo, Visual C#, Visual Basic). A consulta integrada à linguagem permite que as expressões de consulta se beneficiem dos metadados avançados, da verificação de sintaxe em tempo de compilação, da digitação estática e do IntelliSense que estavam disponíveis anteriormente apenas para código imperativo. A consulta integrada à linguagem também permite que um único recurso de consulta declarativa de uso geral seja aplicado a todas as informações na memória, não apenas a informações de fontes externas.

O .NET Language-Integrated Query define um conjunto de operadores de consulta padrão de uso geral que permitem que operações de passagem, filtro e projeção sejam expressas de maneira direta, mas declarativa em qualquer . Linguagem de programação baseada em NET. Os operadores de consulta padrão permitem que as consultas sejam aplicadas a qualquer fonte de informações baseada em T> IEnumerable<. O LINQ permite que terceiros aumentem o conjunto de operadores de consulta padrão com novos operadores específicos de domínio apropriados para o domínio ou tecnologia de destino. Mais importante, terceiros também são livres para substituir os operadores de consulta padrão por suas próprias implementações que fornecem serviços adicionais, como avaliação remota, tradução de consulta, otimização e assim por diante. Ao aderir às convenções do padrão LINQ, essas implementações desfrutam da mesma integração de linguagem e suporte à ferramenta que os operadores de consulta padrão.

A extensibilidade da arquitetura de consulta é usada no próprio projeto LINQ para fornecer implementações que funcionam em dados XML e SQL. Os operadores de consulta por XML (LINQ to XML) usam uma instalação XML eficiente e fácil de usar na memória para fornecer funcionalidade XPath/XQuery na linguagem de programação do host. Os operadores de consulta sobre dados relacionais (LINQ to SQL) baseiam-se na integração de definições de esquema baseadas em SQL ao sistema de tipos CLR (Common Language Runtime). Essa integração fornece forte digitação sobre dados relacionais, mantendo o poder expressivo do modelo relacional e o desempenho da avaliação de consulta diretamente no repositório subjacente.

Introdução com operadores de consulta padrão

Para ver a consulta integrada à linguagem no trabalho, começaremos com um programa C# 3.0 simples que usa os operadores de consulta padrão para processar o conteúdo de uma matriz:

using System;
using System.Linq;
using System.Collections.Generic;

class app {
  static void Main() {
    string[] names = { "Burke", "Connor", "Frank", 
                       "Everett", "Albert", "George", 
                       "Harris", "David" };

    IEnumerable<string> query = from s in names 
                               where s.Length == 5
                               orderby s
                               select s.ToUpper();

    foreach (string item in query)
      Console.WriteLine(item);
  }
}

Se você compilasse e executasse este programa, veria isso como saída:

BURKE
DAVID
FRANK
To understand how language-integrated query works, we need to dissect the
 first statement of our program.
IEnumerable<string> query = from s in names 
                           where s.Length == 5
                           orderby s
                           select s.ToUpper();

A consulta de variável local é inicializada com uma expressão de consulta. Uma expressão de consulta opera em uma ou mais fontes de informações aplicando um ou mais operadores de consulta dos operadores de consulta padrão ou operadores específicos do domínio. Essa expressão usa três dos operadores de consulta padrão: Where, OrderBy e Select.

O Visual Basic 9.0 também dá suporte a LINQ. Aqui está a instrução anterior escrita no Visual Basic 9.0:

Dim query As IEnumerable(Of String) = From s in names _
                                     Where s.Length = 5 _
                   Order By s _
                   Select s.ToUpper()

As instruções C# e Visual Basic mostradas aqui usam expressões de consulta. Assim como a instrução foreach , as expressões de consulta são uma abreviação declarativa conveniente sobre o código que você pode escrever manualmente. As instruções acima são semanticamente idênticas à seguinte sintaxe explícita mostrada em C#:

IEnumerable<string> query = names 
                            .Where(s => s.Length == 5) 
                            .OrderBy(s => s)
                            .Select(s => s.ToUpper());

Essa forma de consulta é chamada de consulta baseada em método . Os argumentos para os operadores Where, OrderBy e Select são chamados de expressões lambda, que são fragmentos de código muito parecidos com delegados. Eles permitem que os operadores de consulta padrão sejam definidos individualmente como métodos e amarrados juntos usando a notação de ponto. Juntos, esses métodos formam a base para uma linguagem de consulta extensível.

Recursos de linguagem que dão suporte ao projeto LINQ

O LINQ é criado inteiramente com base em recursos de linguagem de uso geral, alguns dos quais são novos no C# 3.0 e no Visual Basic 9.0. Cada um desses recursos tem utilitário por conta própria, mas coletivamente esses recursos fornecem uma maneira extensível de definir consultas e APIs consultáveis. Nesta seção, exploramos esses recursos de linguagem e como eles contribuem para um estilo de consultas muito mais direto e declarativo.

Expressões lambda e árvores de expressão

Muitos operadores de consulta permitem que o usuário forneça uma função que executa filtragem, projeção ou extração de chave. As instalações de consulta se baseiam no conceito de expressões lambda, que fornecem aos desenvolvedores uma maneira conveniente de escrever funções que podem ser passadas como argumentos para avaliação subsequente. As expressões Lambda são semelhantes aos delegados CLR e devem aderir a uma assinatura de método definida por um tipo delegado. Para ilustrar isso, podemos expandir a instrução acima para um formulário equivalente, mas mais explícito, usando o tipo de delegado func :

Func<string, bool>   filter  = s => s.Length == 5;
Func<string, string> extract = s => s;
Func<string, string> project = s => s.ToUpper();

IEnumerable<string> query = names.Where(filter) 
                                 .OrderBy(extract)
                                 .Select(project);

Expressões lambda são a evolução natural de métodos anônimos no C# 2.0. Por exemplo, poderíamos ter escrito o exemplo anterior usando métodos anônimos como este:

Func<string, bool>   filter  = delegate (string s) {
                                   return s.Length == 5; 
                               };

Func<string, string> extract = delegate (string s) { 
                                   return s; 
                               };

Func<string, string> project = delegate (string s) {
                                   return s.ToUpper(); 
                               };

IEnumerable<string> query = names.Where(filter) 
                                 .OrderBy(extract)
                                 .Select(project);

Em geral, o desenvolvedor é livre para usar métodos nomeados, métodos anônimos ou expressões lambda com operadores de consulta. As expressões Lambda têm a vantagem de fornecer a sintaxe mais direta e compacta para criação. Mais importante, as expressões lambda podem ser compiladas como código ou dados, o que permite que expressões lambda sejam processadas em runtime por otimizadores, tradutores e avaliadores.

O namespace System.Linq.Expressions define um tipo genérico diferenciado, Expression<T>, que indica que uma árvore de expressão é desejada para uma determinada expressão lambda em vez de um corpo de método tradicional baseado em IL. As árvores de expressão são representações eficientes de dados na memória de expressões lambda e tornam a estrutura da expressão transparente e explícita.

A determinação de se o compilador emitirá IL executável ou uma árvore de expressão é determinada pela forma como a expressão lambda é usada. Quando uma expressão lambda é atribuída a uma variável, campo ou parâmetro cujo tipo é um delegado, o compilador emite IL idêntico ao de um método anônimo. Quando uma expressão lambda é atribuída a uma variável, campo ou parâmetro cujo tipo é Expression<T> para algum tipo delegado T, o compilador emite uma árvore de expressão.

Por exemplo, considere as duas declarações de variáveis a seguir:

Func<int, bool>             f = n => n < 5;
Expression<Func<int, bool>> e = n => n < 5;

A variável f é uma referência a um delegado que é executável diretamente:

bool isSmall = f(2); // isSmall is now true

A variável e é uma referência a uma árvore de expressão que não é executável diretamente:

bool isSmall = e(2); // compile error, expressions == data

Ao contrário dos delegados, que são código efetivamente opaco, podemos interagir com a árvore de expressão como qualquer outra estrutura de dados em nosso programa.

Expression<Func<int, bool>> filter = n => n < 5;

BinaryExpression    body  = (BinaryExpression)filter.Body;
ParameterExpression left  = (ParameterExpression)body.Left;
ConstantExpression  right = (ConstantExpression)body.Right;

Console.WriteLine("{0} {1} {2}", 
                  left.Name, body.NodeType, right.Value);

O exemplo acima decompõe a árvore de expressão em runtime e imprime a seguinte cadeia de caracteres:

n LessThan 5

Essa capacidade de tratar expressões como dados em runtime é fundamental para habilitar um ecossistema de bibliotecas de terceiros que aproveitam as abstrações de consulta base que fazem parte da plataforma. A implementação de acesso a dados LINQ to SQL aproveita essa instalação para traduzir árvores de expressão para instruções T-SQL adequadas para avaliação no repositório.

Métodos de Extensão

As expressões lambda são uma parte importante da arquitetura de consulta. Os métodos de extensão são outros. Os métodos de extensão combinam a flexibilidade de "digitação de pato" popular em linguagens dinâmicas com o desempenho e a validação em tempo de compilação de linguagens tipadas estaticamente. Com os métodos de extensão, terceiros podem aumentar o contrato público de um tipo com novos métodos, permitindo que autores de tipos individuais forneçam sua própria implementação especializada desses métodos.

Os métodos de extensão são definidos em classes estáticas como métodos estáticos, mas são marcados com o atributo [System.Runtime.CompilerServices.Extension] em metadados CLR. Os idiomas são incentivados a fornecer uma sintaxe direta para métodos de extensão. Em C#, os métodos de extensão são indicados pelo modificador que deve ser aplicado ao primeiro parâmetro do método de extensão. Vamos examinar a definição do operador de consulta mais simples, Em que:

namespace System.Linq {
  using System;
  using System.Collections.Generic;

  public static class Enumerable {
    public static IEnumerable<T> Where<T>(
             this IEnumerable<T> source,
             Func<T, bool> predicate) {

      foreach (T item in source)
        if (predicate(item))
          yield return item;
    }
  }
}

O tipo do primeiro parâmetro de um método de extensão indica a que tipo a extensão se aplica. No exemplo acima, o método de extensão Where estende o tipo IEnumerable<T>. Como Where é um método estático, podemos invocá-lo diretamente como qualquer outro método estático:

IEnumerable<string> query = Enumerable.Where(names, 
                                          s => s.Length < 6);

No entanto, o que torna os métodos de extensão exclusivos é que eles também podem ser invocados usando a sintaxe da instância:

IEnumerable<string> query = names.Where(s => s.Length < 6);

Os métodos de extensão são resolvidos em tempo de compilação com base em quais métodos de extensão estão no escopo. Quando um namespace é importado com uma instrução using em C# ou uma instrução Import no Visual Basic, todos os métodos de extensão definidos por classes estáticas desse namespace são colocados no escopo.

Os operadores de consulta padrão são definidos como métodos de extensão no tipo System.Linq.Enumerable. Ao examinar os operadores de consulta padrão, você observará que todos, exceto alguns deles, são definidos em termos da interface T> IEnumerable<. Isso significa que cada fonte de informações compatível com T> IEnumerable< obtém os operadores de consulta padrão simplesmente adicionando a seguinte instrução using em C#:

using System.Linq; // makes query operators visible

Os usuários que desejam substituir os operadores de consulta padrão para um tipo específico podem: definir seus próprios métodos com o mesmo nome no tipo específico por assinaturas compatíveis ou definir novos métodos de extensão de mesmo nome que estendem o tipo específico. Os usuários que desejam evitar completamente os operadores de consulta padrão simplesmente não podem colocar System.Linq no escopo e escrever seus próprios métodos de extensão para IEnumerable<T>.

Os métodos de extensão recebem a prioridade mais baixa em termos de resolução e só são usados se não houver correspondência adequada no tipo de destino e em seus tipos base. Isso permite que os tipos definidos pelo usuário forneçam seus próprios operadores de consulta que têm precedência sobre os operadores padrão. Por exemplo, considere a seguinte coleção personalizada:

public class MySequence : IEnumerable<int> {
  public IEnumerator<int> GetEnumerator() {
    for (int i = 1; i <= 10; i++) 
      yield return i; 
  }

  IEnumerator IEnumerable.GetEnumerator() {
    return GetEnumerator(); 
  }

  public IEnumerable<int> Where(Func<int, bool> filter) {
    for (int i = 1; i <= 10; i++) 
      if (filter(i)) 
        yield return i;
  }
}

Dada essa definição de classe, o programa a seguir usará a implementação MySequence.Where , não o método de extensão, pois os métodos de instância têm precedência sobre os métodos de extensão:

MySequence s = new MySequence();
foreach (int item in s.Where(n => n > 3))
    Console.WriteLine(item);

O operador OfType é um dos poucos operadores de consulta padrão que não estende uma fonte de informações baseada em T> IEnumerable<. Vamos examinar o operador de consulta OfType :

public static IEnumerable<T> OfType<T>(this IEnumerable source) {
  foreach (object item in source) 
    if (item is T) 
      yield return (T)item;
}

OfType aceita não apenas fontes baseadas em T> IEnumerable<, mas também fontes escritas na interface IEnumerable não parametrizada que estava presente na versão 1.0 do .NET Framework. O operador OfType permite que os usuários apliquem os operadores de consulta padrão a coleções clássicas do .NET como esta:

// "classic" cannot be used directly with query operators
IEnumerable classic = new OlderCollectionType();

// "modern" can be used directly with query operators
IEnumerable<object> modern = classic.OfType<object>();

Neste exemplo, a variável modern produz a mesma sequência de valores que a clássica. No entanto, seu tipo é compatível com o código T IEnumerable<> moderno, incluindo os operadores de consulta padrão.

O operador OfType também é útil para fontes de informações mais recentes, pois permite filtrar valores de uma origem com base no tipo . Ao produzir a nova sequência, OfType simplesmente omite membros da sequência original que não são compatíveis com o argumento de tipo. Considere este programa simples que extrai cadeias de caracteres de uma matriz heterogênea:

object[] vals = { 1, "Hello", true, "World", 9.1 };
IEnumerable<string> justStrings = vals.OfType<string>();

Quando enumerarmos a variável justStrings em uma instrução foreach , obteremos uma sequência de duas cadeias de caracteres: "Hello" e "World".

Avaliação de consulta adiada

Os leitores observadores podem ter observado que o operador standard Where é implementado usando o constructo de rendimento introduzido no C# 2.0. Essa técnica de implementação é comum para todos os operadores padrão que retornam sequências de valores. O uso do rendimento tem um benefício interessante, que é que a consulta não é realmente avaliada até que seja iterada, seja com uma instrução foreach ou usando manualmente os métodos GetEnumerator e MoveNext subjacentes. Essa avaliação adiada permite que as consultas sejam mantidas como valores baseados em T> IEnumerable< que podem ser avaliados várias vezes, cada vez gerando resultados potencialmente diferentes.

Para muitos aplicativos, esse é exatamente o comportamento desejado. Para aplicativos que desejam armazenar em cache os resultados da avaliação de consulta, dois operadores, ToList e ToArray, são fornecidos que forçam a avaliação imediata da consulta e retornam uma lista<T> ou uma matriz que contém os resultados da avaliação da consulta.

Para ver como funciona a avaliação de consulta adiada, considere este programa que executa uma consulta simples em uma matriz:

// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };

// declare a variable that represents a query
IEnumerable<string> ayes = names.Where(s => s[0] == 'A');

// evaluate the query
foreach (string item in ayes) 
  Console.WriteLine(item);

// modify the original information source
names[0] = "Bob";

// evaluate the query again, this time no "Allen"
foreach (string item in ayes) 
    Console.WriteLine(item);

A consulta é avaliada sempre que a variável ayes é iterada. Para indicar que uma cópia armazenada em cache dos resultados é necessária, podemos simplesmente acrescentar um operador ToList ou ToArray à consulta como esta:

// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };

// declare a variable that represents the result
// of an immediate query evaluation
string[] ayes = names.Where(s => s[0] == 'A').ToArray();

// iterate over the cached query results
foreach (string item in ayes) 
    Console.WriteLine(item);

// modifying the original source has no effect on ayes
names[0] = "Bob";

// iterate over result again, which still contains "Allen"
foreach (string item in ayes)
    Console.WriteLine(item);

ToArray e ToList forçam a avaliação imediata da consulta. O mesmo vale para os operadores de consulta padrão que retornam valores singleton (por exemplo: First, ElementAt, Sum, Average, All, Any).

A interface T> IQueryable<

O mesmo modelo de execução adiada geralmente é desejado para fontes de dados que implementam a funcionalidade de consulta usando árvores de expressão, como LINQ to SQL. Essas fontes de dados podem se beneficiar da implementação da interface T> IQueryable< para a qual todos os operadores de consulta exigidos pelo padrão LINQ são implementados usando árvores de expressão. Cada IQueryable<T> tem uma representação de "o código necessário para executar a consulta" na forma de uma árvore de expressão. Todos os operadores de consulta adiada retornam um novo IQueryable<T> que aumenta essa árvore de expressão com uma representação de uma chamada para esse operador de consulta. Assim, quando se torna hora de avaliar a consulta, normalmente porque o IQueryable<T> é enumerado, a fonte de dados pode processar a árvore de expressão que representa toda a consulta em um lote. Por exemplo, uma consulta de LINQ to SQL complicada obtida por várias chamadas para operadores de consulta pode resultar em apenas uma única consulta SQL sendo enviada para o banco de dados.

O benefício para os implementadores de fonte de dados de reutilizar essa funcionalidade de adiamento implementando a interface T>IQueryable<é óbvio. Para os clientes que gravam as consultas, por outro lado, é uma grande vantagem ter um tipo comum para fontes de informações remotas. Ele não só permite que eles gravem consultas polimórficas que podem ser usadas em diferentes fontes de dados, mas também abre a possibilidade de gravar consultas que atravessam domínios.

Inicializando valores compostos

Expressões lambda e métodos de extensão nos fornecem tudo o que precisamos para consultas que simplesmente filtram membros de uma sequência de valores. A maioria das expressões de consulta também executa projeção sobre esses membros, transformando efetivamente membros da sequência original em membros cujo valor e tipo podem ser diferentes do original. Para dar suporte à gravação dessas transformações, o LINQ depende de um novo constructo chamado inicializadores de objeto para criar novas instâncias de tipos estruturados. Para o restante deste documento, vamos supor que o seguinte tipo foi definido:

public class Person {
  string name;
  int age;
  bool canCode;

  public string Name {
    get { return name; } set { name = value; }
  }

  public int Age {
    get { return age; } set { age = value; }
  }

  public bool CanCode {
    get { return canCode; } set { canCode = value; }
  }
}

Os inicializadores de objeto nos permitem construir facilmente valores com base nos campos públicos e nas propriedades de um tipo. Por exemplo, para criar um novo valor do tipo Pessoa, podemos escrever esta instrução:

Person value = new Person {
    Name = "Chris Smith", Age = 31, CanCode = false
};

Semanticamente, essa instrução é equivalente à seguinte sequência de instruções:

Person value = new Person();
value.Name = "Chris Smith";
value.Age = 31;
value.CanCode = false;

Inicializadores de objeto são um recurso importante para consulta integrada à linguagem, pois permitem a construção de novos valores estruturados em contextos em que apenas expressões são permitidas (como em expressões lambda e árvores de expressão). Por exemplo, considere essa expressão de consulta que cria um novo valor person para cada valor na sequência de entrada:

IEnumerable<Person> query = names.Select(s => new Person {
    Name = s, Age = 21, CanCode = s.Length == 5
});

A sintaxe de inicialização de objeto também é conveniente para inicializar matrizes de valores estruturados. Por exemplo, considere essa variável de matriz inicializada usando inicializadores de objeto individuais:

static Person[] people = {
  new Person { Name="Allen Frances", Age=11, CanCode=false },
  new Person { Name="Burke Madison", Age=50, CanCode=true },
  new Person { Name="Connor Morgan", Age=59, CanCode=false },
  new Person { Name="David Charles", Age=33, CanCode=true },
  new Person { Name="Everett Frank", Age=16, CanCode=true },
};

Valores e tipos estruturados

O projeto LINQ dá suporte a um estilo de programação centrado em dados no qual alguns tipos existem principalmente para fornecer uma "forma" estática sobre um valor estruturado em vez de um objeto completo com estado e comportamento. Levando essa premissa para sua conclusão lógica, muitas vezes é o caso de que todo o desenvolvedor se preocupa é com a estrutura do valor e a necessidade de um tipo nomeado para essa forma é de pouco uso. Isso leva à introdução de tipos anônimos que permitem que novas estruturas sejam definidas "embutidas" com sua inicialização.

Em C#, a sintaxe para tipos anônimos é semelhante à sintaxe de inicialização de objeto, exceto pelo nome do tipo ser omitido. Por exemplo, considere as duas seguintes instruções:

object v1 = new Person {
    Name = "Brian Smith", Age = 31, CanCode = false
};

object v2 = new { // note the omission of type name
    Name = "Brian Smith", Age = 31, CanCode = false
};

As variáveis v1 e v2 apontam para um objeto na memória cujo tipo CLR tem três propriedades públicas Name, Age e CanCode. As variáveis diferem em que v2 refere-se a uma instância de um tipo anônimo. Em termos CLR, tipos anônimos não são diferentes de qualquer outro tipo. O que torna os tipos anônimos especiais é que eles não têm nenhum nome significativo em sua linguagem de programação. A única maneira de criar instâncias de um tipo anônimo é usando a sintaxe mostrada acima.

Para permitir que as variáveis se refiram a instâncias de tipos anônimos, mas ainda se beneficiam da digitação estática, o C# introduz variáveis locais de tipo implícito: o var palavra-chave pode ser usado no lugar do nome do tipo para declarações de variáveis locais. Por exemplo, considere este programa C# 3.0 legal:

var s = "Bob";
var n = 32;
var b = true;

O palavra-chave var informa ao compilador para inferir o tipo da variável do tipo estático da expressão usada para inicializar a variável. Neste exemplo, os tipos de s, n e b são cadeia de caracteres, int e bool, respectivamente. Este programa é idêntico ao seguinte:

string s = "Bob";
int    n = 32;
bool   b = true;

O palavra-chave var é uma conveniência para variáveis cujos tipos têm nomes significativos, mas é uma necessidade para variáveis que se referem a instâncias de tipos anônimos.

var value = new { 
  Name = " Brian Smith", Age = 31, CanCode = false
};

No exemplo acima, o valor da variável é de um tipo anônimo cuja definição é equivalente ao seguinte pseudo-C#:

internal class ??? {
  string _Name;
  int    _Age;
  bool   _CanCode;

  public string Name { 
    get { return _Name; } set { _Name = value; }
  }

  public int Age{ 
    get { return _Age; } set { _Age = value; }
  }

  public bool CanCode { 
    get { return _CanCode; } set { _CanCode = value; }
  }

  public bool Equals(object obj) { ... }

  public bool GetHashCode() { ... }
}

Tipos anônimos não podem ser compartilhados entre os limites do assembly; no entanto, o compilador garante que haja no máximo um tipo anônimo para uma determinada sequência de pares de nome/tipo de propriedade em cada assembly.

Como os tipos anônimos geralmente são usados em projeções para selecionar um ou mais membros de um valor estruturado existente, podemos simplesmente referenciar campos ou propriedades de outro valor na inicialização de um tipo anônimo. Isso resulta no novo tipo anônimo obtendo uma propriedade cujo nome, tipo e valor são copiados da propriedade ou do campo referenciado.

Por exemplo, considere este exemplo que cria um novo valor estruturado combinando propriedades de outros valores:

var bob = new Person { Name = "Bob", Age = 51, CanCode = true };
var jane = new { Age = 29, FirstName = "Jane" };

var couple = new {
    Husband = new { bob.Name, bob.Age },
    Wife = new { Name = jane.FirstName, jane.Age }
};

int    ha = couple.Husband.Age; // ha == 51
string wn = couple.Wife.Name;   // wn == "Jane"

A referência de campos ou propriedades mostradas acima é simplesmente uma sintaxe conveniente para escrever a seguinte forma mais explícita:

var couple = new {
    Husband = new { Name = bob.Name, Age = bob.Age },
    Wife = new { Name = jane.FirstName, Age = jane.Age }
};

Em ambos os casos, a variável de casal obtém sua própria cópia das propriedades Name e Age de bob e jane.

Tipos anônimos são usados com mais frequência na cláusula select de uma consulta. Por exemplo, considere a consulta abaixo:

var query = people.Select(p => new { 
               p.Name, BadCoder = p.Age == 11
           });

foreach (var item in query) 
  Console.WriteLine("{0} is a {1} coder", 
                     item.Name,
                     item.BadCoder ? "bad" : "good");

Neste exemplo, conseguimos criar uma nova projeção sobre o tipo Pessoa que correspondia exatamente à forma necessária para nosso código de processamento, mas ainda nos deu os benefícios de um tipo estático.

Mais operadores de consulta padrão

Além dos recursos de consulta básicos descritos acima, vários operadores fornecem maneiras úteis de manipular sequências e redigir consultas, dando ao usuário um alto grau de controle sobre o resultado dentro da estrutura conveniente dos operadores de consulta padrão.

Classificação e agrupamento

Em geral, a avaliação de uma consulta resulta em uma sequência de valores produzidos em alguma ordem intrínseca nas fontes de informações subjacentes. Para dar aos desenvolvedores controle explícito sobre a ordem na qual esses valores são produzidos, os operadores de consulta padrão são definidos para controlar a ordem. O mais básico desses operadores é o operador OrderBy .

Os operadores OrderBy e OrderByDescending podem ser aplicados a qualquer fonte de informação e permitir que o usuário forneça uma função de extração de chave que produz o valor usado para classificar os resultados. OrderBy e OrderByDescending também aceitam uma função de comparação opcional que pode ser usada para impor uma ordem parcial sobre as chaves. Vamos examinar um exemplo básico:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

// unity sort
var s1 = names.OrderBy(s => s); 
var s2 = names.OrderByDescending(s => s);

// sort by length
var s3 = names.OrderBy(s => s.Length); 
var s4 = names.OrderByDescending(s => s.Length);

As duas primeiras expressões de consulta produzem novas sequências baseadas na classificação dos membros da origem com base na comparação de cadeia de caracteres. As duas segundas consultas produzem novas sequências baseadas na classificação dos membros da origem com base no comprimento de cada cadeia de caracteres.

Para permitir vários critérios de classificação, OrderBy e OrderByDescending retornam OrderedSequence<T> em vez do IEnumerable<T> genérico. Dois operadores são definidos apenas em OrderedSequence<T>, ou seja, ThenBy e ThenByDescending que aplicam um critério de classificação adicional (subordinado). ThenBy/ThenByDescending retornam OrderedSequence<T>, permitindo que qualquer número de operadores ThenBy/ThenByDescending seja aplicado:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var s1 = names.OrderBy(s => s.Length).ThenBy(s => s);

Avaliar a consulta referenciada por s1 neste exemplo produziria a seguinte sequência de valores:

"Burke", "David", "Frank", 
"Albert", "Connor", "George", "Harris", 
"Everett"

Além da família orderby de operadores, os operadores de consulta padrão também incluem um operador Reverse . Inverter simplesmente enumera em uma sequência e produz os mesmos valores em ordem inversa. Ao contrário de OrderBy, Reverse não considera os próprios valores reais na determinação da ordem, mas depende apenas da ordem em que os valores são produzidos pela fonte subjacente.

O operador OrderBy impõe uma ordem de classificação em uma sequência de valores. Os operadores de consulta padrão também incluem o operador GroupBy , que impõe um particionamento sobre uma sequência de valores com base em uma função de extração de chave. O operador GroupBy retorna uma sequência de valores IGrouping , um para cada valor de chave distinto que foi encontrado. Um IGrouping é um IEnumerable que contém adicionalmente a chave que foi usada para extrair seu conteúdo:

public interface IGrouping<K, T> : IEnumerable<T> {
  public K Key { get; }
}

A aplicação mais simples do GroupBy tem esta aparência:

string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

// group by length
var groups = names.GroupBy(s => s.Length);

foreach (IGrouping<int, string> group in groups) {
    Console.WriteLine("Strings of length {0}", group.Key);

    foreach (string value in group)
        Console.WriteLine("  {0}", value);
}    

Quando executado, este programa imprime o seguinte:

Strings of length 6
  Albert
  Connor
  George
  Harris
Strings of length 5
  Burke
  David
  Frank
Strings of length 7
  Everett

A la Select, GroupBy permite que você forneça uma função de projeção usada para preencher membros dos grupos.

string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

// group by length
var groups = names.GroupBy(s => s.Length, s => s[0]);
foreach (IGrouping<int, char> group in groups) {
    Console.WriteLine("Strings of length {0}", group.Key);

    foreach (char value in group)
        Console.WriteLine("  {0}", value);
}  

Essa variação imprime o seguinte:

Strings of length 6
  A
  C
  G
  H
Strings of length 5
  B
  D
  F
Strings of length 7
  E

Nota Neste exemplo, o tipo projetado não precisa ser o mesmo que a origem. Nesse caso, criamos um agrupamento de inteiros para caracteres de uma sequência de cadeias de caracteres.

Operadores de agregação

Vários operadores de consulta padrão são definidos para agregar uma sequência de valores em um único valor. O operador de agregação mais geral é Aggregate, que é definido desta forma:

public static U Aggregate<T, U>(this IEnumerable<T> source, 
                                U seed, Func<U, T, U> func) {
  U result = seed;

  foreach (T element in source) 
      result = func(result, element);

  return result;
}

O operador Aggregate simplifica a execução de um cálculo em uma sequência de valores. A agregação funciona chamando a expressão lambda uma vez para cada membro da sequência subjacente. Cada vez que Aggregate chama a expressão lambda, ela passa o membro da sequência e um valor agregado (o valor inicial é o parâmetro de semente para Aggregate). O resultado da expressão lambda substitui o valor agregado anterior e Aggregate retorna o resultado final da expressão lambda.

Por exemplo, este programa usa Aggregate para acumular a contagem total de caracteres em uma matriz de cadeias de caracteres:

string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

int count = names.Aggregate(0, (c, s) => c + s.Length);
// count == 46

Além do operador De agregação de uso geral, os operadores de consulta padrão também incluem um operador de Contagem de finalidade geral e quatro operadores de agregação numérica (Min, Max, Sum e Average) que simplificam essas operações comuns de agregação. As funções de agregação numéricas funcionam em sequências de tipos numéricos (por exemplo, int, double, decimal) ou sobre sequências de valores arbitrários, desde que uma função seja fornecida para que os membros da sequência sejam projetados em um tipo numérico.

Este programa ilustra ambas as formas do operador Sum descrito:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

int total1 = numbers.Sum();            // total1 == 55
int total2 = names.Sum(s => s.Length); // total2 == 46

Nota A segunda instrução Sum é equivalente ao exemplo anterior usando Aggregate.

Selecione vs. SelectMany

O operador Select requer a função de transformação para produzir um valor para cada valor na sequência de origem. Se a função de transformação retornar um valor que é uma sequência, cabe ao consumidor percorrer as sub-sequências manualmente. Por exemplo, considere este programa que divide cadeias de caracteres em tokens usando o método String.Split existente:

string[] text = { "Albert was here", 
                  "Burke slept late", 
                  "Connor is happy" };

var tokens = text.Select(s => s.Split(' '));

foreach (string[] line in tokens)
    foreach (string token in line)
        Console.Write("{0}.", token);

Quando executado, este programa imprime o seguinte texto:

Albert.was.here.Burke.slept.late.Connor.is.happy.

Idealmente, gostaríamos que nossa consulta tivesse retornado uma sequência agrupada de tokens e não exposto a cadeia de caracteres intermediária [] ao consumidor. Para isso, usamos o operador SelectMany em vez do operador Select . O operador SelectMany funciona de forma semelhante ao operador Select . É diferente porque espera-se que a função de transformação retorne uma sequência que, em seguida, é expandida pelo operador SelectMany . Aqui está nosso programa reescrito usando SelectMany:

string[] text = { "Albert was here", 
                  "Burke slept late", 
                  "Connor is happy" };

var tokens = text.SelectMany(s => s.Split(' '));

foreach (string token in tokens)
    Console.Write("{0}.", token);

O uso de SelectMany faz com que cada sequência intermediária seja expandida como parte da avaliação normal.

SelectMany é ideal para combinar duas fontes de informações:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var query = names.SelectMany(n => 
                     people.Where(p => n.Equals(p.Name))
                 );

Na expressão lambda passada para SelectMany, a consulta aninhada se aplica a uma fonte diferente, mas tem no escopo o n parâmetro passado da origem externa. Assim , as pessoas. Onde é chamado uma vez para cada n, com as sequências resultantes niveladas por SelectMany para a saída final. O resultado é uma sequência de todas as pessoas cujo nome aparece na matriz de nomes .

Operadores de junção

Em um programa orientado a objetos, objetos relacionados entre si normalmente serão vinculados a referências de objeto que são fáceis de navegar. O mesmo geralmente não se aplica a fontes de informações externas, em que as entradas de dados geralmente não têm nenhuma opção a não ser "apontar" umas para as outras simbolicamente, com IDs ou outros dados que podem identificar exclusivamente a entidade apontada. O conceito de junções refere-se à operação de reunir os elementos de uma sequência com os elementos com os qual eles "correspondem" de outra sequência.

O exemplo anterior com SelectMany realmente faz exatamente isso, combinando cadeias de caracteres com pessoas cujos nomes são essas cadeias de caracteres. No entanto, para essa finalidade específica, a abordagem SelectMany não é muito eficiente— ela percorrerá todos os elementos de pessoas para cada elemento de nomes. Ao reunir todas as informações desse cenário, as duas fontes de informação e as "chaves" pelas quais elas são correspondidas, em uma chamada de método, o operador Join é capaz de fazer um trabalho muito melhor:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var query = names.Join(people, n => n, p => p.Name, (n,p) => p);

Isso é um pouco de boca cheia, mas veja como as peças se encaixam: o método Join é chamado na fonte de dados "externa", nomes. O primeiro argumento é a fonte de dados "interna", pessoas. O segundo e o terceiro argumentos são expressões lambda para extrair chaves dos elementos das fontes externa e interna, respectivamente. Essas chaves são o que o método Join usa para corresponder aos elementos. Aqui queremos que os próprios nomes correspondam à propriedade Name das pessoas. A expressão lambda final é então responsável por produzir os elementos da sequência resultante: ela é chamada com cada par de elementos correspondentes n e p e é usada para moldar o resultado. Nesse caso, optamos por descartar o n e retornar o p. O resultado final é a lista de elementos Person de pessoas cujo Nome está na lista de nomes.

Um primo mais poderoso de Join é o operador GroupJoin . GroupJoin difere de Join na forma como a expressão lambda de formatação de resultados é usada: em vez de ser invocada com cada par individual de elementos externos e internos, ela será chamada apenas uma vez para cada elemento externo, com uma sequência de todos os elementos internos que correspondem a esse elemento externo. Para tornar isso concreto:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var query = names.GroupJoin(people, n => n, p => p.Name,                   
                 (n, matching) => 
                      new { Name = n, Count = matching.Count() }
);

Essa chamada produz uma sequência dos nomes com os quais você começou emparelhado com o número de pessoas que têm esse nome. Assim, o operador GroupJoin permite que você baseie seus resultados em todo o "conjunto de correspondências" para um elemento externo.

Sintaxe da consulta

A instrução foreach existente em C# fornece uma sintaxe declarativa para iteração nos métodos IEnumerable/IEnumerator do .NET Frameworks. A instrução foreach é estritamente opcional, mas provou ser um mecanismo de linguagem muito conveniente e popular.

Com base nesse precedente, as expressões de consulta simplificam as consultas com uma sintaxe declarativa para os operadores de consulta mais comuns: Where, Join, GroupJoin, Select, SelectMany, GroupBy, OrderBy, ThenBy, OrderByDescending, ThenByDescending e Cast.

Vamos começar examinando a consulta simples com a qual começamos este artigo:

IEnumerable<string> query = names 
                            .Where(s => s.Length == 5) 
                            .OrderBy(s => s)
                            .Select(s => s.ToUpper());

Usando uma expressão de consulta, podemos reescrever esta instrução exata desta forma:

IEnumerable<string> query = from s in names 
                            where s.Length == 5
                            orderby s
                            select s.ToUpper();

Como a instrução foreach em C#, as expressões de consulta são mais compactas e fáceis de ler, mas são completamente opcionais. Cada expressão que pode ser gravada como uma expressão de consulta tem uma sintaxe correspondente (embora mais detalhada) usando a notação de ponto.

Vamos começar examinando a estrutura básica de uma expressão de consulta. Cada expressão de consulta sintactica em C# começa com uma cláusula from e termina com uma cláusula select ou group . A cláusula inicial de pode ser seguida por zero ou mais de cláusulas, let, where, join e orderby . Cada cláusula from é um gerador que introduz uma variável de intervalo em uma sequência; cada cláusula let dá um nome ao resultado de uma expressão; e cada cláusula where é um filtro que exclui itens do resultado. Cada cláusula join correlaciona uma nova fonte de dados com os resultados das cláusulas anteriores. Uma cláusula orderby especifica uma ordenação para o resultado:

query-expression ::= from-clause query-body

query-body ::= 

      query-body-clause* final-query-clause query-continuation?

query-body-clause ::=
 (from-clause 
      | join-clause 
      | let-clause 
      | where-clause 
      | orderby-clause)

from-clause ::=from itemName in srcExpr

join-clause ::=join itemName in srcExpr on keyExpr equals keyExpr 
       (into itemName)?

let-clause ::=let itemName = selExpr

where-clause ::= where predExpr

orderby-clause ::= orderby (keyExpr (ascending | descending)?)*

final-query-clause ::=
 (select-clause | groupby-clause)

select-clause ::= select selExpr

groupby-clause ::= group selExpr by keyExprquery-continuation ::= intoitemName query-body

Por exemplo, considere estas duas expressões de consulta:

var query1 = from p in people
             where p.Age > 20
             orderby p.Age descending, p.Name
             select new { 
                 p.Name, Senior = p.Age > 30, p.CanCode
             };

var query2 = from p in people
             where p.Age > 20
             orderby p.Age descending, p.Name
             group new { 
                p.Name, Senior = p.Age > 30, p.CanCode
             } by p.CanCode;

O compilador trata essas expressões de consulta como se tivessem sido escritas usando a seguinte notação de ponto explícita:

var query1 = people.Where(p => p.Age > 20)
                   .OrderByDescending(p => p.Age)
                   .ThenBy(p => p.Name)
                   .Select(p => new { 
                       p.Name, 
                       Senior = p.Age > 30, 
                       p.CanCode
                   });

var query2 = people.Where(p => p.Age > 20)
                   .OrderByDescending(p => p.Age)
                   .ThenBy(p => p.Name)
                   .GroupBy(p => p.CanCode, 
                            p => new {
                                   p.Name, 
                                   Senior = p.Age > 30, 
                                   p.CanCode
                   });

As expressões de consulta passam por uma tradução mecânica em chamadas de métodos com nomes específicos. A implementação exata do operador de consulta escolhida depende, portanto, do tipo das variáveis que estão sendo consultadas e dos métodos de extensão que estão no escopo.

As expressões de consulta mostradas até agora usaram apenas um gerador. Quando mais de um gerador é usado, cada gerador subsequente é avaliado no contexto de seu antecessor. Por exemplo, considere essa pequena modificação em nossa consulta:

var query = from s1 in names 
            where s1.Length == 5
            from s2 in names 
            where s1 == s2
            select s1 + " " + s2;

Quando executado em relação a essa matriz de entrada:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

obtemos os seguintes resultados:

Burke Burke
Frank Frank
David David

A expressão de consulta acima se expande para esta expressão dot-notation:

var query = names.Where(s1 => s1.Length == 5)
                 .SelectMany(s1 => names, (s1,s2) => new {s1,s2})
                 .Where($1 => $1.s1 == $1.s2) 
                 .Select($1 => $1.s1 + " " + $1.s2);

Nota Esta versão do SelectMany usa uma expressão lambda extra que é usada para produzir o resultado com base em elementos das sequências externa e interna. Nessa expressão lambda, as duas variáveis de intervalo são coletadas em um tipo anônimo. O compilador inventa um nome de variável $1 para denotar esse tipo anônimo em expressões lambda subsequentes.

Um tipo especial de gerador é a cláusula join , que introduzirá elementos de outra fonte que correspondem aos elementos das cláusulas anteriores de acordo com as chaves fornecidas. Uma cláusula join pode produzir os elementos correspondentes um por um, mas se especificado com uma cláusula into , os elementos correspondentes serão fornecidos como um grupo:

var query = from n in names
            join p in people on n equals p.Name into matching
            select new { Name = n, Count = matching.Count() };

Não é surpresa que essa consulta se expanda diretamente para uma que já vimos antes:

var query = names.GroupJoin(people, n => n, p => p.Name,                   
           (n, matching) => 
                      new { Name = n, Count = matching.Count() }
);

Geralmente, é útil tratar os resultados de uma consulta como um gerador em uma consulta subsequente. Para dar suporte a isso, as expressões de consulta usam o em palavra-chave para unir uma nova expressão de consulta após uma cláusula select ou group. Isso é chamado de continuação de consulta.

O em palavra-chave é especialmente útil para pós-processamento dos resultados de uma cláusula group by. Por exemplo, considere este programa:

var query = from item in names
            orderby item
            group item by item.Length into lengthGroups
            orderby lengthGroups.Key descending
            select lengthGroups;

foreach (var group in query) { 
    Console.WriteLine("Strings of length {0}", group.Key);

    foreach (var val in group)
        Console.WriteLine("  {0}", val);
}

Este programa gera o seguinte:

Strings of length 7
  Everett
Strings of length 6
  Albert
  Connor
  George
  Harris
Strings of length 5
  Burke
  David
  Frank

Nesta seção, foi descrito como o C# implementa expressões de consulta. Outros idiomas podem optar por dar suporte a operadores de consulta adicionais com sintaxe explícita ou não ter expressões de consulta.

É importante observar que a sintaxe da consulta não é de forma alguma conectada aos operadores de consulta padrão. É um recurso puramente sintactico que se aplica a qualquer coisa que atenda ao padrão de consulta implementando métodos subjacentes com os nomes e assinaturas apropriados. Os operadores de consulta padrão descritos acima fazem isso usando métodos de extensão para aumentar a interface T> IEnumerable<. Os desenvolvedores podem explorar a sintaxe de consulta em qualquer tipo desejado, desde que verifiquem se ela segue o padrão de consulta, seja pela implementação direta dos métodos necessários ou pela adição deles como métodos de extensão.

Essa extensibilidade é explorada no próprio projeto LINQ pelo provisionamento de duas API habilitadas para LINQ, ou seja, LINQ to SQL, que implementa o padrão LINQ para acesso a dados baseados em SQL e LINQ to XML que permite consultas LINQ em dados XML. Ambos são descritos nas seções a seguir.

LINQ to SQL: Integração do SQL

O .NET Language-Integrated Query pode ser usado para consultar armazenamentos de dados relacionais sem sair da sintaxe ou do ambiente de tempo de compilação da linguagem de programação local. Essa instalação, chamada de código LINQ to SQL, aproveita a integração das informações do esquema SQL aos metadados clr. Essa integração compila as definições de tabela e exibição do SQL em tipos CLR que podem ser acessados de qualquer idioma.

LINQ to SQL define dois atributos principais, [Tabela] e [Coluna], que indicam quais tipos e propriedades CLR correspondem a dados SQL externos. O atributo [Tabela] pode ser aplicado a uma classe e associa o tipo CLR a uma tabela ou exibição SQL nomeada. O atributo [Coluna] pode ser aplicado a qualquer campo ou propriedade e associa o membro a uma coluna SQL nomeada. Ambos os atributos são parametrizados para permitir que metadados específicos do SQL sejam retidos. Por exemplo, considere essa definição de esquema SQL simples:

create table People (
    Name nvarchar(32) primary key not null, 
    Age int not null, 
    CanCode bit not null
)

create table Orders (
    OrderID nvarchar(32) primary key not null, 
    Customer nvarchar(32) not null, 
    Amount int
)

O equivalente clr tem esta aparência:

[Table(Name="People")]
public class Person {
  [Column(DbType="nvarchar(32) not null", Id=true)]
  public string Name; 

  [Column]
  public int Age;

  [Column]
  public bool CanCode;
}

[Table(Name="Orders")]
public class Order {
  [Column(DbType="nvarchar(32) not null", Id=true)]
  public string OrderID; 

  [Column(DbType="nvarchar(32) not null")]        
  public string Customer; 

  [Column]
  public int? Amount; 
}

Nota Este exemplo de que colunas anuláveis são mapeadas para tipos anuláveis no CLR (tipos anuláveis apareceram pela primeira vez na versão 2.0 do .NET Framework) e que, para tipos SQL que não têm uma correspondência 1:1 com um tipo CLR (por exemplo, nvarchar, char, text), o tipo SQL original é mantido nos metadados CLR.

Para emitir uma consulta em um repositório relacional, a implementação LINQ to SQL do padrão LINQ converte a consulta de seu formulário de árvore de expressão em uma expressão SQL e ADO.NET objeto DbCommand adequado para avaliação remota. Por exemplo, considere esta consulta simples:

// establish a query context over ADO.NET sql connection
DataContext context = new DataContext(
     "Initial Catalog=petdb;Integrated Security=sspi");

// grab variables that represent the remote tables that 
// correspond to the Person and Order CLR types
Table<Person> custs = context.GetTable<Person>();
Table<Order> orders   = context.GetTable<Order>();

// build the query
var query = from c in custs
            from o in orders
            where o.Customer == c.Name
            select new { 
                       c.Name, 
                       o.OrderID,
                       o.Amount,
                       c.Age
            }; 

// execute the query
foreach (var item in query) 
    Console.WriteLine("{0} {1} {2} {3}", 
                      item.Name, item.OrderID, 
                      item.Amount, item.Age);

O tipo DataContext fornece um tradutor leve que converte os operadores de consulta padrão para SQL. O DataContext usa o ADO.NET IDbConnection existente para acessar o repositório e pode ser inicializado com um objeto de conexão ADO.NET estabelecido ou uma cadeia de conexão que pode ser usada para criar um.

O método GetTable fornece variáveis compatíveis com IEnumerable que podem ser usadas em expressões de consulta para representar a tabela ou exibição remota. As chamadas para GetTable não causam nenhuma interação com o banco de dados, em vez disso, representam o potencial de interagir com a tabela ou exibição remota usando expressões de consulta. Em nosso exemplo acima, a consulta não é transmitida para o repositório até que o programa itera sobre a expressão de consulta, nesse caso, usando a instrução foreach em C#. Quando o programa itera pela primeira vez na consulta, o computador DataContext converte a árvore de expressão na seguinte instrução SQL que é enviada para o repositório:

SELECT [t0].[Age], [t1].[Amount], 
       [t0].[Name], [t1].[OrderID]
FROM [Customers] AS [t0], [Orders] AS [t1]
WHERE [t1].[Customer] = [t0].[Name]

É importante observar que, ao criar a funcionalidade de consulta diretamente na linguagem de programação local, os desenvolvedores obtêm todo o poder do modelo relacional sem precisar assar estaticamente as relações no tipo CLR. Esse mapeamento relacional/objeto abrangente declarado também pode aproveitar esse recurso de consulta principal para usuários que desejam essa funcionalidade. LINQ to SQL fornece funcionalidade de mapeamento relacional de objeto com a qual o desenvolvedor pode definir e navegar nas relações entre objetos. Você pode se referir a Pedidos como uma propriedade da classe Customer usando mapeamento, para que você não precise de junções explícitas para unir os dois. Os arquivos de mapeamento externos permitem que o mapeamento seja separado do modelo de objeto para recursos de mapeamento mais avançados.

LINQ to XML: Integração XML

O .NET Language-Integrated Query for XML (LINQ to XML) permite que os dados XML sejam consultados usando os operadores de consulta padrão, bem como operadores específicos de árvore que fornecem navegação semelhante a XPath por meio de descendentes, ancestrais e irmãos. Ele fornece uma representação eficiente na memória para XML que se integra à infraestrutura de leitor/gravador de System.Xml existente e é mais fácil de usar do que o DOM W3C. Há três tipos que fazem a maior parte do trabalho de integração de XML com consultas: XName, XElement e XAttribute.

O XName fornece uma maneira fácil de usar para lidar com os QNames (identificadores qualificados por namespace) usados como nomes de elemento e atributo. O XName manipula a atomização eficiente de identificadores de forma transparente e permite que símbolos ou cadeias de caracteres simples sejam usados sempre que um QName for necessário.

Elementos e atributos XML são representados usando XElement e XAttribute , respectivamente. XElement e XAttribute dão suporte à sintaxe de construção normal, permitindo que os desenvolvedores escrevam expressões XML usando uma sintaxe natural:

var e = new XElement("Person", 
                     new XAttribute("CanCode", true),
                     new XElement("Name", "Loren David"),
                     new XElement("Age", 31));

var s = e.ToString();

Isso corresponde ao seguinte XML:

<Person CanCode="true">
  <Name>Loren David</Name> 
  <Age>31</Age> 
</Person>

Observe que nenhum padrão de fábrica baseado em DOM foi necessário para criar a expressão XML e que a implementação de ToString produziu o XML textual. Os elementos XML também podem ser construídos a partir de um XmlReader existente ou de um literal de cadeia de caracteres:

var e2 = XElement.Load(xmlReader);
var e1 = XElement.Parse(
@"<Person CanCode='true'>
  <Name>Loren David</Name>
  <Age>31</Age>
</Person>");

O XElement também dá suporte à emissão de XML usando o tipo XmlWriter existente.

XElement dovetails com os operadores de consulta, permitindo que os desenvolvedores escrevam consultas em informações não XML e produzam resultados XML construindo XElements no corpo de uma cláusula select:

var query = from p in people 
            where p.CanCode
            select new XElement("Person", 
                                  new XAttribute("Age", p.Age),
                                  p.Name);

Essa consulta retorna uma sequência de XElements. Para permitir que XElements seja criado com base no resultado desse tipo de consulta, o construtor XElement permite que sequências de elementos sejam passadas diretamente como argumentos:

var x = new XElement("People",
                  from p in people 
                  where p.CanCode
                  select 
                    new XElement("Person", 
                                   new XAttribute("Age", p.Age),
                                   p.Name));

Essa expressão XML resulta no seguinte XML:

<People>
  <Person Age="11">Allen Frances</Person> 
  <Person Age="59">Connor Morgan</Person> 
</People>

A instrução acima tem uma tradução direta para o Visual Basic. No entanto, o Visual Basic 9.0 também dá suporte ao uso de literais XML, que permitem que expressões de consulta sejam expressas usando uma sintaxe XML declarativa diretamente do Visual Basic. O exemplo anterior poderia ser construído com a instrução Visual Basic:

 Dim x = _
        <People>
             <%= From p In people __
                 Where p.CanCode _

                 Select <Person Age=<%= p.Age %>>p.Name</Person> _
             %>
        </People>

Os exemplos até agora mostraram como construir novos valores XML usando a consulta integrada à linguagem. Os tipos XElement e XAttribute também simplificam a extração de informações de estruturas XML. O XElement fornece métodos de acessador que permitem que expressões de consulta sejam aplicadas aos eixos XPath tradicionais. Por exemplo, a consulta a seguir extrai apenas os nomes do XElement mostrados acima:

IEnumerable<string> justNames =
    from e in x.Descendants("Person")
    select e.Value;

//justNames = ["Allen Frances", "Connor Morgan"]

Para extrair valores estruturados do XML, simplesmente usamos uma expressão de inicializador de objeto em nossa cláusula select:

IEnumerable<Person> persons =
    from e in x.Descendants("Person")
    select new Person { 
        Name = e.Value,
        Age = (int)e.Attribute("Age") 
    };

Observe que XAttribute e XElement dão suporte a conversões explícitas para extrair o valor de texto como um tipo primitivo. Para lidar com dados ausentes, podemos simplesmente converter em um tipo anulável:

IEnumerable<Person> persons =
    from e in x.Descendants("Person")
    select new Person { 
        Name = e.Value,
        Age = (int?)e.Attribute("Age") ?? 21
    };

Nesse caso, usamos um valor padrão de 21 quando o atributo Age está ausente.

O Visual Basic 9.0 fornece suporte direto à linguagem para os métodos acessadores Elements, Attribute e Descendants do XElement, permitindo que os dados baseados em XML sejam acessados usando uma sintaxe mais compacta e direta chamada propriedades do eixo XML. Podemos usar essa funcionalidade para escrever a instrução C# anterior como esta:

Dim persons = _
      From e In x...<Person> _   
      Select new Person { _
          .Name = e.Value, _
          .Age = IIF(e.@Age, 21) _
      } 

No Visual Basic, x...<Person> obtém todos os itens na coleção Descendants de x com o nome Person, enquanto a expressão e.@Age localiza todos os XAttributes com o nome Age. The Value propriedade obtém o primeiro atributo na coleção e chama a propriedade Value nesse atributo.

Resumo

O .NET Language-Integrated Query adiciona recursos de consulta ao CLR e às linguagens direcionadas a ele. O recurso de consulta baseia-se em expressões lambda e árvores de expressão para permitir que predicados, projeções e expressões de extração de chave sejam usados como código executável opaco ou como dados transparentes na memória adequados para processamento ou tradução downstream. Os operadores de consulta padrão definidos pelo projeto LINQ funcionam em qualquer fonte de informações baseada em T> IEnumerable< e são integrados com ADO.NET (LINQ to SQL) e System.Xml (LINQ to XML) para permitir que dados relacionais e XML obtenham os benefícios da consulta integrada à linguagem.

Operadores de consulta padrão em um Nutshell

Operador Descrição
Where Operador de restrição com base na função de predicado
Select/SelectMany Operadores de projeção com base na função seletora
Take/Skip/ TakeWhile/SkipWhile Operadores de particionamento com base na função de posição ou predicado
Join/GroupJoin Operadores de junção com base em funções de seletor de chave
Concat operador Concatenation
OrderBy/ThenBy/OrderByDescending/ThenByDescending Classificando operadores classificando em ordem crescente ou decrescente com base em funções opcionais de seletor de chave e comparador
Reverse Operador de classificação invertendo a ordem de uma sequência
GroupBy Operador de agrupamento com base em funções opcionais de seletor de chave e comparador
Distinct Definir operador removendo duplicatas
União/Intersecção Definir operadores que retornam união ou interseção do conjunto
Except Definir o operador que retorna a diferença de conjunto
AsEnumerable Operador de conversão para IEnumerable<T>
ToArray/ToList Operador de conversão em matriz ou Lista<T>
ToDictionary/ToLookup Operadores de conversão para Dicionário<K, T> ou Pesquisa<K,T> (vários dicionários) com base na função seletora de chave
OfType/Cast Operadores de conversão para IEnumerable<T> com base na filtragem por ou conversão em argumento de tipo
SequenceEqual Igualdade do operador verificando a igualdade de elementos pairwise
First/FirstOrDefault/Last/LastOrDefault/Single/SingleOrDefault Operadores de elemento que retornam o elemento inicial/final/somente com base na função de predicado opcional
ElementAt/ElementAtOrDefault Operadores de elemento que retornam elemento com base na posição
DefaultIfEmpty Operador de elemento substituindo sequência vazia por sequência singleton com valor padrão
Intervalo Operador de geração retornando números em um intervalo
Repetir Operador de geração retornando várias ocorrências de um determinado valor
Vazio Operador de geração retornando uma sequência vazia
Qualquer/Todos Verificação de quantificador para satisfação existencial ou universal da função de predicado
Contém Quantificador verificando a presença de um determinado elemento
Count/LongCount Operadores de agregação que contam elementos com base na função de predicado opcional
Sum/Min/Max/Average Operadores de agregação com base em funções seletoras opcionais
Agregado Operador de agregação acumulando vários valores com base na função de acumulação e na semente opcional