Introdução à linguagem LINQ - .Net Magazine 77
O Microsoft .NET Framework incluiu a partir da versão 3.5 a linguagem de consulta integrada LINQ. Esta linguagem será demonstrada neste artigo visando o seu imediato emprego pelo programador.
As consultas de dados usando LINQ são uma maneira mais fácil de seleção de dados, quer estes estejam na memória em coleções de objetos, em um banco de dados ou em um arquivo XML. Sendo integrada com o ambiente do framework, é possível escrever as expressões de consulta dentro do próprio código em C#.
Guia do artigo:
- Arquitetura
- Operadores
- Ordenação
- LINQ to Objects
- Consultando Bancos de Dados
- LINQ to Entity
- LINQ to XML
- Criando o projeto
- Configurando o formulário
- Demonstrando LINQ to Objects
- LINQ to Entity - Demonstrando Consulta a Banco de Dados
- Configurando o formulário para consulta com Banco de Dados
- Cliente=p.Orders.Customers.CompanyName
- Consultando dados de um documento XML
As aplicações modernas, principalmente usando o framework .net, necessitam de agilidade na consulta a dados. Você pode usar LINQ para recuperar os registros de um pedido de venda e demonstrá-los em um DataGrid, pode fazer comparações entre os dados carregados na memória verificando a existência de um determinado conjunto de valores ou, como também é muito comum, necessitar ler os dados de um arquivo XML no disco ou ainda, criar um arquivo para armazenar preferências do usuário para o seu programa. Todas estas tarefas são executadas mais intuitivamente com a linguagem LINQ.
Muito tem sido publicado sobre a linguagem LINQ. Entretanto, por ser uma novidade no campo das consultas, pode ser um pouco enigmática para quem quer começar a usá-la em seus projetos. Para você poder empregá-la de maneira prática e imediata, este artigo irá passar os principais elementos, mostrando as três principais divisões que são: LINQ to Objects, LINQ to Entities e LINQ to XML. A primeira versão é usada para consultar coleções de dados na memória. LINQ to Entity tem sido constantemente desenvolvido para ser a opção em uma consulta de dados armazenados em bancos de dados e finalmente, se estiver lidando com arquivo XML, você vai considerar LINQ to XML bem fácil de compreender com os passos que serão mostrados. Para melhor entendimento será dado um exemplo para cada um destes tipos após uma introdução teórica.
A partir da versão 2.0 do framework foram introduzidas as coleções genéricas nos programas desenvolvidos para a plataforma .Net. Com isto, tornou-se mais ágil realizar pesquisas de dados na memória uma vez que fora criada uma ferramenta para manipular listas dinâmicas de objetos mais facilmente. Muitos métodos foram acrescentados e com seu constante uso, o próximo passo foi surgir uma forma de simplificar ainda mais a manipulação destas coleções de dados deixando o código mais limpo e simples de entender.
É onde surge a partir da versão 3.5 do framework .NET a linguagem de consulta de dados integrada, que em inglês é Language Integrated Query, ou, LINQ para os mais íntimos. O primeiro ponto a ser compreendido é o objetivo principal desta linguagem, que é simplesmente facilitar a manipulação dos dados que estiverem em uma coleção. Estes podem ter sua origem em uma lista de objetos implementada através das listas dinâmicas ou qualquer outra classe derivada, podem ser oriundas de registros em uma tabela armazenada em um banco de dados SQL Server ou qualquer outro que seja suportado ou, ainda, serem nós de um arquivo XML.
Qualquer que seja a origem dos dados, a manipulação destes torna-se mais fácil com o uso de LINQ. Esta linguagem está integrada dentro do framework para que você possa usar diretamente junto com o seu código.
Logo, para quem utiliza a linguagem C# é possível inicializar diretamente uma variável com os resultados de uma consulta. Para começar a entender você precisa estar familiarizado com as novidades introduzidas na linguagem C# 3.0, destacando:
- Tipos e métodos anônimos;
- Variáveis tipadas conforme a expressão, ou, declaração da variável sem definir o tipo, utilizando a palavra reservada var. O tipo será dado pela expressão que estará iniciando o valor para a variável;
Inicializadores diretos para objetos e coleções. Em vez de usar a seguinte sintaxe:
Point Ponto = new Point();
Ponto.X = 0;
Ponto.Y = 0;
Você pode usar o seguinte código:
Point P = new Point
{
X = 0,
Y = 0
};
A mesma coisa pode ser feita com as listas anônimas:
List<Point> ListaDePontos = new List<Point>
{
new Point { X = 0, Y = 0 },
new Point { X = 2, Y = 1 },
new Point { X = 4, Y = 3 }
};
Lambda expressions, embora seja possível utilizar LINQ sem este novo recurso.
Arquitetura da linguagem
LINQ está integrada com o framework e possui diversas variações, sendo cada uma apropriada para um uso. A Figura 1 mostra a arquitetura desta linguagem.
Basicamente esta linguagem está voltada para três grandes fontes de dados que são, conforme exposto, coleções de objetos na memória, dados provenientes de bancos de dados relacionais e dados armazenados em arquivos XML. Neste artigo descreverei as três principais subdivisões desta linguagem conforme já colocado anteriormente, porém, antes disto, veremos os elementos comuns a todas elas.
Sintaxe básica de uma consulta com LINQ
A sintaxe básica do LINQ é a seguinte:
var <variável> = from <elemento da lista> in <lista de dados>
where <cláusula/cláusulas de consulta>
select <elemento>
Onde temos os seguintes elementos:
- var: identificador dinâmico do tipo de dados que será retornado. Uma consulta feita com LINQ retorna uma coleção de objetos do tipo System.Collections.Generic.IEnumerable<T>. O uso de “var” indica que o tipo da variável resultante será do mesmo tipo usado para a lista de dados consultada;
- <variável>: nome do objeto que receberá o resultado da pesquisa;
- from: indica a origem dos dados sendo que <elemento da lista> será o nome dado para cada instância da mesma. Por exemplo, se tivermos uma lista de strings, poderemos chamar de str. Cada um dos elementos será do tipo dos dados dentro da lista;
- in: define a lista de origem. Pode ser qualquer coleção de dados como um array ou um List<T>;
- <lista de dados>: coleção dos dados a serem consultados. Como veremos adiante pode ser qualquer lista de dados na memória quer sejam dados provenientes de um array de objetos, dados armazenados em tabelas de um banco de dados ou arquivos XML;
- where: filtro para os dados. Esta cláusula pode usar os operadores de comparação existentes na linguagem C# como será demonstrado mais à frente;
- select: executa a seleção armazenando os resultados. É através desta instrução que os dados são devolvidos para serem utilizados pelo código do programa.
Operadores de comparação
A cláusula where aceita vários operadores de comparação, a Tabela 1 demonstra os principais. Os sinais como, por exemplo, “==” e “!=” são exclusivos para o C# sendo que se você for usar outra linguagem precisará usar os sinais próprios dessa linguagem. Já aqueles baseados em funções são equivalentes tanto para C# como para VB.NET por exemplo.
Operador |
Função |
== |
Usado para igualdade entre dois elementos. |
>= |
Maior ou igual. |
<= |
Menor ou igual. |
!= |
Diferença. |
! |
Operador not pode ser usado em conjunto com outros operadores como o Contains. |
&& |
E (and) lógico. |
|| |
Ou (ou) lógico. |
StartsWith() |
Aplicado quando se deseja comparar o início de uma expressão do tipo string. |
EndsWith() |
Usado para comparar o final de uma expressão do tipo string. |
Contains() |
Verifica se o valor está dentro de qualquer ponto de um valor do tipo string. |
Lembrando de que estes são os principais. Você pode usar estes operadores em conjunto e ainda usar métodos anônimos e lambda expressions para criar seus filtros personalizados.
Nota: Para mantermos o artigo objetivo não irei aprofundar nas extensões que podem ser feitas com LINQ. Consultas usando lambda expressions e métodos anônimos serão deixadas para um artigo futuro. Procure compreender as bases da linguagem e começar a usar com os exemplos que serão dados. Informe-se sobre estes recursos assim que estiver dominando os elementos mais básicos.
Uma opção de consulta que merece destaque com LINQ é que você pode comparar e selecionar apenas o texto a partir de um determinado tamanho através da propriedade Length. Uma consulta com este tipo de critério seria feita da seguinte forma:
string[] nomes = { “Maria”, “João”, “Marcos”, “Paulo”, “Alexandre” };
var selecao = from nome in nomes
where nome.Length > 4
select nome;
Quando você começa a utilizar LINQ para os seus trabalhos, logo percebe que se trata de uma ferramenta poderosa, cheia de recursos e com um alto grau de extensibilidade. Além disto, é a base para os próximos passos a serem dados.
Ordenação dos resultados
A ordenação dos elementos é feita com a declaração orderby seguida do elemento que irá fazer a ordenação. Caso se deseje uma ordenação descendente basta usar após o elemento de ordenação a palavra descending. O exemplo a seguir ordena um vetor numérico de maneira descendente:
int[] numeros = { 3, 9, 0, 50, 3};
var x = from n in numeros
orderby n descending
select n;
LINQ to Objects
Esta é a versão que é usada para filtrar dados que estão na memória. Basicamente qualquer tipo de coleções de dados que implemente a interface IEnumerable<T> pode ser filtrada com LINQ. Assim, você irá fazer suas consultas sobre as seguintes coleções de dados:
- Arrays;
- Listas genéricas de dados – implementadas com List<T>;
- Strings – é possível fazer uma consulta sobre uma única string para, por exemplo, extrair determinada palavra;
- Dicionários genéricos de dados.
Consultando Bancos de Dados
A maior parte das aplicações empresariais trabalha intensivamente com bancos de dados. Logo, se você está aprendendo a utilizar uma ferramenta de programação nova, seu principal interesse recai em como esta poderá ser útil nas tarefas mais corriqueiras. Basicamente você vai procurar se informar como poderá usar LINQ em uma situação do mundo real. Uma boa notícia para os programadores corporativos é que agora vai ser possível manter o acesso aos dados mais próximo do grau de abstração atingido com C# e mais amplamente com as linguagens orientadas a objeto.
Para poder demonstrar isto um pouco melhor considere uma situação bastante corriqueira. Tome por exemplo a tabela Orders do banco de dados Northwind. A Tabela 2 demonstra a estrutura dos campos da tabela.
Nome da Coluna |
Tipo de Dados |
OrderID |
Int |
CustomerID |
nchar(5) |
EmployeeID |
Int |
OrderDate |
Datetime |
RequiredDate |
Datetime |
ShippedDate |
Datetime |
ShipVia |
Int |
Freight |
Money |
ShipName |
nvarchar(40) |
ShipAddress |
nvarchar(60) |
ShipCity |
nvarchar(15) |
ShipRegion |
nvarchar(15) |
ShipPostalCode |
nvarchar(10) |
ShipCountry |
nvarchar(15) |
Se você precisar executar a tarefa simples de listar todas as vendas que foram feitas a partir de uma determinada data, você precisaria resolver os seguintes problemas:
- Estabelecer uma conexão com o banco de dados, cuidando para que fosse uma conexão válida;
- Configurar um objeto, provavelmente um SqlCommand que está disponível no ADO.NET, para usar esta conexão;
- Escrever uma consulta usando a linguagem SQL tomando o cuidado de informar os nomes corretos para as tabelas e para os campos da mesma;
- Se estiver fazendo uma consulta dinâmica, precisará tomar cuidado com os parâmetros passados para evitar problemas de segurança como ataques de SQL Injection;
- Ler os dados que forem retornados e mostrar para o usuário novamente tomando o cuidado de usar os nomes corretos para os campos e principalmente verificar os tipos de dados retornados.
Apenas a título de exemplo, se isto fosse feito em um programa do tipo console, o código ficaria mais ou menos parecido com o da Listagem 1.
using System;
using System.Data.SqlClient;
public class Class1
{
public static void Main( string[] args )
{
// define o objeto de conexão com o banco de dados
using (SqlConnection SqlCon =
new SqlConnection(
"Data Source=vladimir-note;Initial Catalog=Northwind;Integrated Security=True" ))
{
// define o objeto que fará a consulta
using (SqlCommand SqlCmd = new SqlCommand
{
Connection = SqlCon,
CommandText = @"
select * from orders
where orderDate >= @orderDate"
})
{
// configuração do parâmetro passado
DateTime orderDate = new DateTime(1990,1,1);
// envia o parâmetro à consulta
SqlCmd.Parameters.AddWithValue("@orderDate", orderDate);
// abre a consulta com o banco de dados
SqlCon.Open();
// recebe os registros passados pela consulta
using (SqlDataReader dr = SqlCmd.ExecuteReader())
{
// percorre e imprime os registros
while (dr.Read())
Console.WriteLine( "\t{1:dd/MM/yyyy}\t{2:C}",
dr["OrderID"],
dr["OrderDate"],
dr["Freight"]);
}
// fecha a conexão com o banco de dados
SqlCon.Close();
}
}
}
}
Um pouco extenso não achou? Mas analisando o código percebemos que os elementos vistos anteriormente, para a solução do problema (consultar a tabela “Orders”) estão todos ali. Da linha 1 até a 7 temos os elementos comuns para uma aplicação do tipo console. Acrescentamos apenas a biblioteca System.Data.SqlClient para poder usar as classes para manipulação de dados do ADO.NET, que fazem a ponte entre o .NET e o banco de dados.
Na linha 9 cria-se a instância do objeto para conexão com o banco de dados passando a string de conexão e abaixo, na linha 14 é definido um objeto SqlCommand contendo a instrução SQL que irá executar a consulta. Mais à frente, na linha 23 eu crio uma variável do tipo DateTime para poder passar os parâmetros para a consulta na linha 25. Seguindo em frente abrimos a conexão com o banco de dados (linha 27) e nas próximas linhas exibimos o conteúdo dos campos.
Nota: Se você já utiliza C# e o Visual Studio há algum tempo, deve concordar comigo que este trabalho pode ser feito utilizando-se os datasets tipados com os componentes do tipo TableAdapter, fazendo este trabalho mais facilmente. Entretanto, mesmo usando este tipo de abordagem, o Visual Studio cria um código muito parecido com este apresentado aqui.
Eu gostaria de chamar a atenção para alguns aspectos deste tipo de código. Primeiramente, perdeu-se um pouco da abstração fornecida pelas linguagens orientadas a objeto quando se escreveu a instrução SQL nas linhas 18 e 19. É muito difícil fazer o mapeamento objeto-relacional quando se está lidando com bancos de dados relacionais e isso faz com que o código do programa contenha partes orientadas a objeto e parte destinada a lidar com as tabelas no banco.
Nota do DevManMapeamento objeto-relacional é o nome que se dá à tarefa de fazer a ponte entre tabelas de um banco de dados relacional e objetos de sistema. A maneira mais simples de se realizar isso é definir que toda tabela se torne uma classe de negócio, porém existem situações no lado orientado a objetos que são difíceis de se aplicar, ou simplesmente não existem no mundo relacional. Herança é um exemplo. Essa falta de compatibilidade entre as duas tecnologias é chamada de impedância objeto relacional. Para suprir essa falta vários frameworks foram desenvolvidos, os chamados ORM (Object Relational Mapper). Estes oferecem recursos para facilitar o mapeamento de classes para tabelas e vice-versa. Dentre os existentes, merecem destaque o NHibernate e o recente Entity Framework da Microsoft.
A instrução SQL tem como desvantagem não poder ser verificada em tempo de compilação. Se por um acaso estiver incorreta, somente será percebido o erro em tempo de execução. Também não se consegue verificar os nomes dos elementos envolvidos na instrução SQL até se executar o programa e a validação dos tipos dos parâmetros e dos dados retornados só será feita durante a execução do programa. O modelo usado atualmente para manipular dados oferece alguns avanços mas, deixa a desejar nos pontos citados. Nesta brecha é que surge a linguagem LINQ. Mas não iremos utilizá-la diretamente conectada ao banco de dados. Para isso adicionaremos um elemento que irá fazer o mapeamento objeto-relacional.
ADO.NET Entity Framework e LINQ to Entity
Antes de continuar dê uma boa olhada na Figura 1. Para utilizar LINQ com banco de dados temos que compreender um pouco do ADO.NET Entity Framework. Como foi colocado anteriormente, usa-se LINQ para fazer consultas de coleções de objetos na memória e, para poder utilizar a linguagem com dados vindos de bancos de dados, os objetos do banco precisam estar mapeados como classes dentro do projeto. E é neste ponto que um novo elemento torna-se presente. Este é o objetivo das bibliotecas do ADO.NET Entity Framework.
Com o conjunto de bibliotecas e classes presentes neste framework, você poderá mapear qualquer banco de dados que possua um provider desenvolvido para ele. O papel de ADO.NET Entity Framework é integrar o banco de dados e permitir o acesso representando tabelas como classes e registros como coleções de dados na memória. O mapeamento pode ser feito tanto visualmente como via código, embora você irá preferir fazer da primeira forma. Mas, mesmo com as facilidades para a geração das classes vinculadas com o banco de dados, estas são geradas no projeto como partial class, sendo assim, você pode adicionar funcionalidades editando os arquivos das classes para customizar seu comportamento.
Durante o mapeamento objeto-relacional não só as tabelas e seus campos são codificados para o seu projeto bem como os relacionamentos existentes entre estas tabelas são mapeados, ficando tudo mais fácil de utilizar e compreender, inclusive com uma representação visual destes relacionamentos.
As tabelas mapeadas no projeto com o Entity Framework são armazenadas em um arquivo com a extensão .edmx e podem ser visualizadas na Figura 2. Note os relacionamentos entre as tabelas Products, Categories e Order Details.
Todo o código necessário para consultar estas tabelas é gerado automaticamente no projeto, inclusive você pode visualizá-lo abrindo o arquivo de designer (extensão .designer.cs na janela Solution Explorer). A Listagem 2 mostra uma parte deste código.
/// <summary>
/// There are no comments for NorthwindModel.Categories in the schema.
/// </summary>
/// <KeyProperties>
/// CategoryID
/// </KeyProperties>
[global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="NorthwindModel", Name="Categories")]
[global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)]
[global::System.Serializable()]
public partial class Categories : global::System.Data.Objects.DataClasses.EntityObject
{
/// <summary>
/// Create a new Categories object.
/// </summary>
/// <param name="categoryID">Initial value of CategoryID.</param>
/// <param name="categoryName">Initial value of CategoryName.</param>
[global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")]
public static Categories CreateCategories(int categoryID, string categoryName)
{
Categories categories = new Categories();
categories.CategoryID = categoryID;
categories.CategoryName = categoryName;
return categories;
}
...
Na Listagem 2 vemos parte do código utilizado para a tabela Categories. Este código é gerado automaticamente pelo compilador e dificilmente você precisará fazer alguma manutenção. Coloquei aqui somente para que você entenda que o Entity Framework representa apenas uma camada de abstração adicionada ao projeto para que seja possível trabalhar com os dados de uma maneira mais vinculada com a sintaxe da linguagem C# (ou a que você estiver usando em seu projeto), além de fazer todo o trabalho para que usando LINQ sejam possíveis várias consultas, inclusive relacionamentos existentes entre as tabelas. Será possível fazer as tradicionais operações de inclusão, atualização e exclusão de registros no banco de dados, pois estas capacidades são suportadas por instruções específicas. No exemplo que será construído mostrarei como fazer o mapeamento das tabelas no seu projeto e também alguns exemplos de consultas com LINQ to Entity.
Nota do DevManVocê já deve ter ouvido falar de outra opção para utilizar LINQ com banco de dados chamada LINQ to SQL. Esta forma permite apenas conexões com bancos de dados SQL Server e Sql Server Compact. Apesar de apresentar várias otimizações para este banco de dados, não irei tratar da mesma aqui para não prolongar demais. Por outro lado, este tipo de acesso tem sido pouco encorajado quanto ao uso pela comunidade de desenvolvedores por existirem rumores de que não será incluído em versões futuras do .NET Framework
.
LINQ to XML
Nos trabalhos de desenvolvimento de software cedo ou tarde precisaremos trabalhar com arquivos XML. Quer seja para armazenar dados de configurações de suas aplicações, quer seja para permitir o intercâmbio de dados entre programas distintos destacando-se o seu uso intensivo em Web Services, os arquivos XML são largamente empregados em qualquer plataforma.
O .NET framework proporciona a biblioteca System.Xml que é baseada em DOM (Document Object Model) para representar os documentos XML. Esta biblioteca representou vários avanços na manipulação deste tipo de arquivo e certamente, muitas aplicações já utilizam estas funcionalidades.
Entretanto, LINQ to XML pretende trazer a manipulação destes arquivos para um nível ainda mais intuitivo, como faz com objetos na memória. Vamos fazer uma comparação. Vamos gerar um documento XML utilizando os recursos disponíveis no System.Xml e em seguida, demonstrar esta mesma operação feita com LINQ to XML. Considere o arquivo XML da Listagem 3.
<artigos>
<titulo>Introdução à linguagem LINQ</titulo>
<subtitulo>Compreendendo LINQ para sua imediata aplicação</subtitulo>
<autor>Vladimir Rech</autor>
<revista>Dot Net Magazine</revista>
<editora>Dev Media</editora>
</artigos>
Para criarmos este arquivo usando a biblioteca System.Xml e escrevermos o seu conteúdo na tela de um programa console, o código seria parecido com o da Listagem 4.
// declara o documento
XmlDocument doc = new XmlDocument();
// cria os nós
XmlElement artigos = doc.CreateElement("artigos");
XmlElement titulo = doc.CreateElement("titulo");
// inclui o conteúdo
titulo.InnerText = "Introdução à linguagem LINQ";
XmlElement subtitulo = doc.CreateElement("subtitulo");
subtitulo.InnerText = "Compreendendo LINQ para sua imediata aplicação";
XmlElement autor = doc.CreateElement("autor");
autor.InnerText = "Vladimir Rech";
XmlElement revista = doc.CreateElement("revista");
revista.InnerText = "Dot Net Magazine";
XmlElement editora = doc.CreateElement("editora");
editora.InnerText = "Dev Media";
// envia os nós para o arquivo
artigos.AppendChild(titulo);
artigos.AppendChild(subtitulo);
artigos.AppendChild(autor);
artigos.AppendChild(revista);
artigos.AppendChild(editora);
doc.AppendChild(artigos);
// objeto para escrever o conteúdo
XmlTextWriter writer = new XmlTextWriter(Console.Out);
writer.Formatting = Formatting.Indented;
doc.WriteContentTo(writer);
Analisando rapidamente o código você percebe que precisa declarar cada elemento expressamente e ainda cuidar da hierarquia do documento. Agora, vejamos como isso seria feito com o uso de LINQ (Listagem 5).
// declara o arquivo xml
var xml = new XElement("artigos",
new XElement("titulo", "Introdução à Linguagem LINQ to XML"),
new XElement("subtitulo", "Compreendendo LINQ para sua imediata aplicação"),
new XElement("autor", "Vladimir Rech"),
new XElement("revista", "Dot Net Magazine"),
new XElement("editora", "Dev Media"));
// escreve o conteúdo na tela
Console.WriteLine(xml);
Acredito que não preciso fazer nenhum comentário. Qual das duas implementações você prefere?
A linguagem LINQ se baseia nos mesmos princípios DOM para a modelagem, entretanto, oferece uma implementação mais intuitiva a partir de vários elementos, com os quais podemos criar os nós, elementos, atributos, comentários e tudo o mais que faz parte destes elementos. Vejamos quais os principais métodos de LINQ to XML para criar o arquivo:
- XName: é usado para criar uma identificação para o namespace do XML. É usado tanto para um elemento como para um atributo;
- XNode: representa o nó dentro da árvore do documento XML. É usado como uma classe base para os elementos: XComment, XContainer, XDocumentType, XProcessingInstruction e XText;
- XContainer: derivado de XNode, disponibiliza características e funcionalidades como subelementos de um nó XML, é também a base para as pesquisas pelos nós XML e para XDocument e XElement;
- XDocument: representa um documento XML que contém os principais elementos do documento como a declaração do tipo do documento (XDocumentType), um elemento raiz (XElement) e podendo ter opcionalmente comentários (XComment);
- XElement: é o tipo fundamental para construção de um documento XML devendo ter um XName e opcionalmente um ou mais atributos, podendo ter ou não algum conteúdo;
- XAttribute: é usado para criar atributos que são elementos que possuem uma chave associada a um elemento. Não são nós XML mas estão vinculados dentro destes;
- XComment: é usado para fazer comentários em um nó do documento XML.
A criação dos documentos XML com LINQ é bem facilitada e, embora os exemplos aqui apresentados sejam bem simples, estes podem se tornar complexos conforme a necessidade e a complexidade do documento XML a ser criado. Da mesma forma, a consulta de dados do arquivo XML é bastante simplificada com LINQ. Considere novamente o documento XML da Listagem 3. Para consultar os dados, considerando-se a sintaxe básica da consulta, seria da seguinte forma:
from <variavel> in <origem>.Element(<elemento a ser recuperado do arquivo>)
select (<tipo>)<variavel>.Element(<nome do elemento>)
Sendo que você pode usar este código para, por exemplo, preencher os dados de uma classe pré-definida.
Criando o projeto
Para executar os exemplos deste artigo eu utilizei os seguintes recursos (você vai encontrar no final uma seção com links para fazer downloads dos programas usados):
- Visual Studio 2010. Mas você poderá executar o exemplo também com outras versões, inclusive versões Express;
- Microsoft Sql Server 2008 Express Edition;
- Banco de dados demonstração Northwind. Este é um banco de dados disponibilizado pela Microsoft para que sejam demonstradas as características do Sql Server.
Os exemplos a serem desenvolvidos serão feitos em um projeto do tipo Windows Forms. Neste projeto teremos três abas distintas onde cada uma será usada para demonstrar um tipo específico do LINQ. Abra o Visual Studio e crie um novo projeto do tipo Windows Forms usando a linguagem Visual C# e com o Framework 3.5. Para criar o projeto use o menu File>New>Project. A Figura 3 demonstra a janela onde você irá definir o nome do projeto e escolher as opções de criação do mesmo.
Configurando o formulário
O projeto é criado com um formulário principal já adicionado. Ajuste as propriedades do formulário da seguinte forma: Size para 640;480 e Text para LINQ Demo. Arraste um componente TabControl para o formulário. Acrescente uma página para que o controle fique com três TabPages. A propriedade Dock do controle TabControl deve ser ajustada para Fill.Veja na Figura 4 como deve parecer.
Demonstrando LINQ to Objects
Na primeira aba do formulário será demonstrado o uso de LINQ to Objects consultando uma lista de alguns dos estados brasileiros. Arraste os componentes para o formulário como na Figura 5 e verifique as propriedades de cada componente na Tabela 3.
Componente |
Propriedade |
Valor |
List Box |
(Name) |
lstDados |
Group Box |
(Name) |
groupBox1 |
Radio Button |
(Name) |
rbPesquisarTexto |
Text |
Pesquisar parte do texto |
|
Checked |
True |
|
Radio Button |
(Name) |
rbNumeroDeLetras |
Text |
Numero de Letras |
|
Text Box |
(Name) |
txtPesquisa |
Button |
(Name) |
btnExecutar |
Text |
Executar |
|
List Box |
(Name) |
lstResultado |
A pesquisa irá funcionar da seguinte forma: no componente ListBox denominado lstDados iremos manter uma lista com alguns estados do Brasil. A pesquisa poderá listar somente os estados com um número de letras maior ou igual ao que for digitado no componente TextBox denominado txtPesquisa, se o componente RadioButton selecionado for rbNumeroDeLetras. Caso o RadioButton selecionado seja rbPesquisarTexto, a pesquisa a ser feita será verificar se algum estado da listagem possui (contém) parte do texto digitado. O primeiro passo é criar a lista dos estados. Exiba o código fonte do formulário pressionando a tecla F7. Logo após a declaração da classe do formulário, acrescente o código da Listagem 6.
using System;
using System.Windows.Forms;
using System.Linq;
namespace DemoLINQ
{
public partial class Form1 : Form
{
string[] ListaDeEstados = { "Paraná", "Santa Catarina", "Rio Grande Do Sul", "São Paulo",
"Rio de Janeiro", "Minas Gerais", "Espírito Santo", "Bahia",
"Sergipe", "Paraíba", "Ceará" };
...
Esta Listagem 6 demonstra o início da declaração da classe para o formulário. Após a declaração das principais bibliotecas usadas (linha 1 até a 3), na linha 5 temos a declaração do namespace usado no programa. Na linha 7, a declaração da classe do formulário. Note que ele deriva da classe base Form. Na linha 9 criei um array de strings com alguns estados do Brasil, fique à vontade para incluir mais para tornar o exemplo mais interessante. Para popularmos o componente ListBox de forma que exiba estes estados, vamos acrescentar um código para ser executado ao entrarmos na primeira TabPage. Selecione esta página no designer do formulário, pressione F4 para exibir as propriedades. Exiba os eventos e dê um clique duplo sobre o evento Enter, escrevendo em seguida o código da Listagem 7.
private void tabPage1_Enter( object sender, EventArgs e )
{
// popula os dados para a pesquisa com LINQ to Objects
lstDados.DataSource = ListaDeEstados;
}
O código a ser escrito consiste em passar o array ListaDeEstados para a propriedade DataSource do componente ListBox (linha 20). Dê um clique duplo sobre o componente btnExecutar e escreva o código da Listagem 8 para definir o que será feito ao executar a consulta.
private void btnExecutar_Click( object sender, EventArgs e )
{
// executa a consulta
if (rbNumeroDeLetras.Checked)
{
// pesquisa pelo tamanho da palavra
var Resultado = from s in ListaDeEstados
where s.Length >= int.Parse( txtPesquisa.Text )
select s;
// popula a list box com o resultado
lstResultado.DataSource = Resultado.ToList();
}
else
{
// pesquisa pela palavra digitada
var Resultado = from s in ListaDeEstados
where s.ToUpper().Contains(txtPesquisa.Text.ToUpper())
select s;
// popula a list box com o resultado
lstResultado.DataSource = Resultado.ToList();
}
}
Para executar a consulta apropriada, verificando o número de letras ou se o estado contém o texto digitado, iniciamos a rotina verificando se o componente RadioButton rbNumeroDeLetras está selecionado (linha 26). Caso esteja, a pesquisa a ser executada é aquela que verifica o tamanho das palavras comparando-o com o que foi passado. Na linha 30 comparamos a propriedade Length com o valor que foi digitado no componente TextBox fazendo a conversão do valor. Os resultados encontrados são passados para a variável Resultado (linha 29) e enviados como data source para o ListBox lstResultado. Caso o RadioButton selecionado seja aquele que indica a pesquisa de parte do nome, então, a partir da linha 39 verificamos se algum dos nomes contém o conteúdo que foi digitado (linha 40). Note que é feita uma conversão do conteúdo para maiúscula, pois as consultas fazem diferenças entre maiúsculas e minúsculas.
Nota: Observe que não fiz nenhum tipo de tratamento de erro, isto, para manter o código sucinto. Quando estiver trabalhando em projetos do mundo real você precisará verificar se um número válido foi digitado, caso contrário, o sistema irá gerar uma exceção quando encontrar algum valor inválido.
Execute o projeto pressionando a tecla F5. Você deve obter um resultado parecido com o da Figura 6.
LINQ to Entity - Demonstrando Consulta a Banco de Dados
O próximo passo na demonstração é consultar dados armazenados no banco Northwind do SQL Server. Para efeitos de objetividade, não vou demonstrar como instalar este banco e no final do artigo coloquei o endereço para você fazer o download da instalação. Na nossa demonstração iremos fazer três tipos de consulta, uma demonstrando todos os clientes da tabela de clientes, uma segunda mostrando todas as vendas que possuam produtos acima de um determinado valor e outra listando as vendas com os nomes dos clientes.
Começamos fazendo o mapeamento das tabelas Orders, Customers, Products e OrdersDetails no projeto usando ADO.NET Entity Framework. Na janela Solution Explorer clique com o botão direito do mouse sobre o ícone do projeto e escolher Add > New Item. Na janela que se abre escolha o item ADO.NET Entity Data Model e nomeie como Northwind.edmx. Você pode escolher entre gerar um modelo vazio ou a partir de um banco de dados existente, que é o nosso caso. Na próxima janela (Figura 7) escolha o item Generate from database e clique no botão Next.
Você precisa informar como se conectar ao banco de dados. Na próxima janela clique no botão New Connection para configurar a conexão com o banco de dados (Figura 8).
Escolha o servidor apropriado através do campo Server name. Informe o seu tipo de autenticação e selecione o nome do banco de dados, no caso do nosso exemplo, Northwind. Os detalhes estão na Figura 9.
A próxima etapa é selecionar quais objetos do banco de dados serão mapeados através da janela Chose Your Database Objects (Figura 10). Expanda o item Tables e escolha as tabelas Customers, Order Details, Orders e Products. Clique no botão Finish para concluir a tarefa.
Nesta figura você ainda pode decidir se vai querer que ao mapear as tabelas para classes, no seu projeto os nomes sejam modificados para o plural ou sejam mantidos exatamente como estão no banco de dados. Isso é feito através do campo Pluralize or singularize generated object names. Verifique como fica mais interessante para você. No caso da demonstração estou mantendo como veio do banco. É ainda possível que você personalize o nome do namespace a ser utilizado para referenciar as classes criadas no campo Model Namespace. Depois de concluídos estes passos o Visual Studio exibe o modelo visual, como na Figura 11.
Observe que os relacionamentos existentes entre as tabelas já estão demonstrados no modelo. Note também que a tabela Order Details por possuir um caractere de espaço no seu nome, teve de ser renomeada para Order_Details. Uma questão importante é saber onde fica armazenada a conexão com o banco de dados. Este ficará no arquivo App.Config que fica na pasta junto com os fontes do projeto. Este arquivo é convertido para <NomeDoExecutável>.config na pasta bin\debug ou bin\release após o projeto compilado e é um arquivo do tipo XML que pode ser editado para personalizar a localização do banco de dados a ser usado no ambiente de produção. A Listagem 9 mostra um exemplo deste arquivo.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="NorthwindEntities" connectionString="metadata=res://*/NorthWind.csdl|res://*/NorthWind.ssdl|res://*/NorthWind.msl;provider=System.Data.SqlClient;provider connection string="Data Source=vladimir-note;Initial Catalog=Northwind;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>
Configurando o formulário para consulta com Banco de Dados
Abra o design do formulário que estamos trabalhando e selecione a segunda aba. Adicione e configure os componentes configurando como demonstrado na Tabela 4 e os organize como mostra a Figura 12.
Componente |
Propriedade |
Valor |
Label |
Text |
Clientes (Customers) |
Combo Box |
(Name) |
cmbCustomers |
DropDownStyle |
DropDownList |
|
Label |
Text |
Preço Unitário |
TextBox |
(Name) |
txtPrecoUnitario |
Button |
(Name) |
btnVendasPorCliente |
Text |
Vendas por Cliente |
|
Button |
(Name) |
btnFiltrarPeloPreco |
Text |
Filtrar pelo Preço |
|
Label |
Text |
Resultado |
DataGridView |
(Name) |
grdResultado |
Nesta janela o controle cmbCustomers listará todos os clientes cadastrados. Então, vamos escrever um código para assim que entrarmos nesta aba, este controle tenha os dados preenchidos. Selecione o controle tabPage2, que foi onde acabamos de configurar os controles, e na janela Properties dê um duplo clique no evento Enter. Escreva o código como na Listagem 10.
// evento disparado ao entrar na tabPage
private void tabPage2_Enter( object sender, EventArgs e )
{
// configura o datamodel
NorthwindEntities nwModel = new NorthwindEntities();
// faz a consulta
var Customers = from c in nwModel.Customers
select c;
// configura o campo para ser exibido no controle combobox
cmbCustomers.DisplayMember = "CompanyName";
// configura a chave para o controle
cmbCustomers.ValueMember = "CustomerID";
// envia os dados para o controle
cmbCustomers.DataSource = Customers.ToList();
}
Este código da Listagem 10 inicia criando uma instância para o Entity Data Model que criamos e denominamos de NorthwindEntities na linha 51. Isto é necessário, pois é este namespace que mapeia as tabelas do banco de dados para as classes do projeto. A linha 53 armazena os resultados da consulta na variável Customers. Esta consulta é bem simples, apenas retorna todos os registros da tabela. Provavelmente você vai querer fazer algum tipo de filtro posteriormente, mas, veremos mais adiante como fazer isto.
O controle ComboBox pode ser usado para exibir dados retornados de um banco de dados. Para isto, precisamos definir qual será o campo que será exibido através de sua propriedade DisplayMember, como está demonstrado na linha 56. Também precisamos definir o campo que será usado como filtro para consultas posteriores. No caso da tabela Customers é o campo CustomerID que precisa ser passado para a tabela Orders, por isso, na linha 58 informamos este campo para a propriedade ValueMember. E para concluir, passamos os dados da variável Customers, convertendo-os para uma lista, veja método .ToList(), para que seja usada para popular o controle.
Vamos agora definir a consulta que irá filtrar as vendas pelo cliente selecionado no controle ComboBox. Dê um duplo clique no botão btnVendasPorCliente e escreva o código da Listagem 11.
// Consulta vendas pelo cliente
private void btnVendasPorCliente_Click( object sender, EventArgs e )
{
// configura o datamodel
NorthwindEntities nwModel = new NorthwindEntities();
// recebe o ID do cliente para ser passado para a consulta
string IDCliente = cmbCustomers.SelectedValue.ToString();
// executa a consulta trazendo apenas alguns campos
var Vendas = from o in nwModel.Orders
where o.Customers.CustomerID == IDCliente
orderby o.OrderDate descending
select new { o.OrderID, o.OrderDate, o.ShipCity };
// envia os dados para a grid
grdResultado.DataSource = Vendas.ToList();
}
Este código traz os detalhes da venda como número (OrderID), data (OrderDate) e cidade (ShipCity). Para isso, começamos novamente instanciando o Entity Data Model na linha 67. Separamos o ID do cliente para ser passado para a consulta na linha 69. A linha 71 inicia a consulta sendo que desta vez temos algumas particularidades. Note na linha 72 que estamos fazendo referência direta à tabela Customers:
where o.Customers.CustomerID
Esta é mais uma característica forte do LINQ: as tabelas relacionadas são agregadas automaticamente às classes sendo que, as tabelas com chaves estrangeiras são mapeadas dentro da classe principal como uma de suas propriedades.
Na linha 73 fazemos a ordenação descendente dos resultados e na linha 74 um novo elemento na consulta que é a seleção de apenas alguns campos para serem exibidos. Sempre que quiser, em vez de exibir a entidade inteira, exibir apenas alguns campos, enumere-os dentro das chaves {}. O controle DataGridView recebe o resultado da consulta na linha 76. Por fim, quero demonstrar o LINQ filtrando os produtos pelo preço. Para fechar esta demonstração vamos listar o número das vendas com o valor dos produtos iguais ou acima do valor dado e também o nome do produto e o cliente que fez a compra. Você pode perceber que estamos fazendo novamente referência a tabelas relacionadas. Veja na Listagem 12 como isto fica bem simples de se fazer com LINQ.
// Filtrando vendas pelo preço do produto
private void btnFiltrarPeloPreco_Click( object sender, EventArgs e )
{
// recebe o valor do produto
decimal PrecoUnitario = decimal.Parse( txtPrecoUnitario.Text );
// configura o datamodel
NorthwindEntities nwModel = new NorthwindEntities();
// executa a consulta
var Produtos = from p in nwModel.Order_Details
where p.UnitPrice >= PrecoUnitario
orderby p.UnitPrice
select new
{
IDVenda = p.OrderID,
Produto = p.Products.ProductName,
Valor = p.UnitPrice,
Cliente=p.Orders.Customers.CompanyName
};
// envia dados para a grid
grdResultado.DataSource = Produtos.ToList();
}
Novamente temos um código bem simples. Na linha 83 estamos convertendo o valor do produto digitado no controle txtPrecoUnitário.
Nota: Não estou fazendo em nenhum momento tratamento de erros no código, porém, sempre faça isto nos seus códigos, principalmente nas operações de conversões de tipos e consultas a bancos de dados, mesmo usando LINQ. Embora o compilador possa validar o código é possível ainda que ocorram erros não tratados.
Na linha 85 instanciamos novamente o Entity Model sendo que, em projetos maiores você pode deixar isto previamente configurado no seu código. E a partir da linha 87 temos a consulta com novas características. Começamos bem simples fazendo referência à tabela Order_Details que é onde iremos aplicar o filtro do preço na linha 88. Fazemos uma ordenação crescente dos registros na linha seguinte e da linha 90 até a 95 selecionamos quais colunas queremos usar. Neste caso, além de fazer referência aos campos da tabela original, consultamos o nome do produto na tabela referenciada Products e o nome do cliente. Na linha 95 temos uma referência indireta, primeiramente fazemos referência à tabela Orders, diretamente ligada com Order_Details para a partir desta, fazer referência à tabela Customers para poder obter o nome do cliente. A seguir o detalhe do código para você entender melhor:
Cliente=p.Orders.Customers.CompanyName
Nesta consulta além de selecionarmos os campos que desejamos exibir, estamos renomeando os nomes para exibição na grid, ou, se preferir, estamos fazendo uso dos tipos anônimos do C# que é o que está sendo usado na realidade.
Execute o projeto. As Figuras 13 e 14 mostram os resultados do código feito para a consulta com banco de dados que fizemos até agora.
Consultando dados de um documento XML
O passo final é demonstrar como consultar um arquivo XML usando LINQ e preencher um controle DataGridView com este arquivo. O arquivo XML contém os seguintes dados, conforme na Listagem 13.
<?xml version="1.0" encoding="utf-8"?>
<guitarras>
<guitarra NumeroDeSerie="111201STRAT">
<fabricante>Fender</fabricante>
<modelo>Stratocaster</modelo>
<MadeiraCorpo>Alder</MadeiraCorpo>
<MadeiraEscala>Mapple</MadeiraEscala>
<MadeiraBraco>Mapple</MadeiraBraco>
<ano>1990</ano>
</guitarra>
<guitarra NumeroDeSerie="123202STRAT">
<fabricante>Fender</fabricante>
<modelo>Stratocaster</modelo>
<MadeiraCorpo>Mogno</MadeiraCorpo>
<MadeiraEscala>Mapple</MadeiraEscala>
<MadeiraBraco>Rowsewood</MadeiraBraco>
<ano>1989</ano>
</guitarra>
<guitarra NumeroDeSerie="112301TELE">
<fabricante>Fender</fabricante>
<modelo>Telecaster</modelo>
<MadeiraCorpo>Alder</MadeiraCorpo>
<MadeiraEscala>Mapple</MadeiraEscala>
<MadeiraBraco>Mapple</MadeiraBraco>
<ano>2000</ano>
</guitarra>
<guitarra NumeroDeSerie="99901LPAUL">
<fabricante>Gibson</fabricante>
<modelo>Les Paul</modelo>
<MadeiraCorpo>Mogno</MadeiraCorpo>
<MadeiraEscala>Mogno</MadeiraEscala>
<MadeiraBraco>Rosewood</MadeiraBraco>
<ano>1980</ano>
</guitarra>
</guitarras>
Para preencher o controle, vamos criar uma classe que será preenchida com os dados do arquivo. Adicione uma classe ao projeto clicando com o botão direito do mouse sobre o ícone do projeto na janela Solution Explorer e escolhendo a opção Add > New Class (Figura 15).
Na janela que se abre preencha o campo Name com guitarra para definir o nome da classe e clique no botão Add para concluir. O código para esta classe é muito simples sendo que apenas definimos os campos a serem usados. Optei por manter os mesmos nomes dos elementos do arquivo XML. Veja o código na Listagem 14.
namespace DemoLINQ
{
public class guitarra
{
public string modelo { get; set; }
public string MadeiraCorpo { get; set; }
public string MadeiraBraco { get; set; }
public string MadeiraEscala { get; set; }
public string NumeroDeSerie { get; set; }
public string ano { get; set; }
public string fabricante { get; set; }
}
}
Abra o formulário no designer do Visual Studio e adicione os controles como na Tabela 5. Veja na Figura 16 a disposição dos controles.
Componente |
Propriedade |
Valor |
Label |
Text |
Nome do arquivo |
TextBox |
(Name) |
txtArquivoXML |
AutoCompleteMode |
Suggest. (Esta propriedade permite ao controle sugerir o conteúdo a partir de uma origem configurada. Ver a próxima propriedade). |
|
AutoCompleteSource |
FileSystem. Indica onde o controle busca os dados para sugerir para o usuário. Neste caso, vai consultar os arquivos armazenados no disco rígido. |
|
Button |
(Name) |
btnCarregarXML |
Label |
Text |
Resultado |
DataGridView |
(Name) |
grdXML |
Dê um duplo clique sobre o botão btnCarregarXML e escreva o código da Listagem 15.
// le o arquivo xml
private void btnCarregarXML_Click( object sender, EventArgs e )
{
// executa a consulta
var resultado = from guit in XElement.Load( txtArquivoXML.Text ).Elements( "guitarra" )
select new guitarra
{
ano = (string)guit.Element( "ano" ),
fabricante = (string)guit.Element( "fabricante" ),
MadeiraBraco = (string)guit.Element( "MadeiraBraco" ),
MadeiraCorpo = (string)guit.Element( "MadeiraCorpo" ),
MadeiraEscala = (string)guit.Element( "MadeiraEscala" ),
modelo = (string)guit.Element( "modelo" ),
NumeroDeSerie = (string)guit.Attribute( "NumeroDeSerie" )
};
// preenche a grid com os resultados
grdXML.DataSource = resultado.ToList();
}
Neste código estou demonstrando um uso típico para a consulta de arquivos XML com LINQ. Na primeira linha (linha 106) é carregado o arquivo do disco a partir do endereço informado no controle txtArquivoXML. O método XElement.Load faz a carga do arquivo que deve estar presente no endereço informado. Com o método .Elements informamos para a instrução ler somente os elementos guitarra dentro do arquivo XML.
Em seguida na linha 107 e seguintes, é criada uma instância da classe guitarra para ser enviada para a lista de resultados indicada por “var resultado”. Cada elemento precisa ser convertido para string e atribuído para uma propriedade da classe através da instrução guit.Element. Esta instrução lê um nó do arquivo XML dentro do elemento raiz passado anteriormente. Você deve qualificar qual o elemento será lido dentro dos parâmetros. Lembrando que os arquivos XML fazem distinção entre maiúsculas e minúsculas.
Na linha 115, em vez de ler um elemento do arquivo XML, precisamos ler o conteúdo de um atributo, então, em vez de utilizar “.Element” usamos “.Attribute” passando como parâmetro o nome do atributo a ser lido. Novamente passamos o resultado para o controle “DataGridView” na linha 118 convertendo para uma lista. Pressione F5 e veja o programa sendo executado como na Figura 17.
Conclusão
Uma das principais vantagens do uso da linguagem LINQ para se fazer consultas é a possibilidade de consultar dados usando a mesma sintaxe usada pela linguagem que se está trabalhando (C# ou Visual Basic) diminuindo a distância que é gerada quando usamos, por exemplo, ADO.NET e instruções SQL. Certamente a curva de aprendizado é um pouco acentuada, entretanto, com um pouco de esforço e mantendo o foco em coisas mais simples, você consegue iniciar a aplicação desta ferramenta imediatamente em seus projetos, criando aplicações mais ágeis e um código mais fácil de ser entendido por manter o foco na orientação a objetos, o que é difícil de fazer quando se está trabalhando, por exemplo, com dados relacionais. A linguagem LINQ é extensa e pode ser expandida permitindo inúmeras variações. Além disto, existem assuntos paralelos como o Entity Framework que merecem um pouco mais de atenção e estudo para que você domine completamente. Também o uso de LINQ to XML é vasto e o que foi apresentado neste artigo pretendeu apenas servir de porta de entrada para seu uso inicial. Procure se informar mais sobre todos estes assuntos e tire proveito dos recursos oferecidos.
Referências:
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Artigo