segunda-feira, 26 de maio de 2008

Linq to Objects - Consultando dados em memória

Click here to see the English version of this post

Olá a todos. O assunto deste post continua sobre a tecnologia Linq, mas desta vez vamos ver um segmento específico: Linq to Objects.

Antes de começar vou recomendar a todos o livro sobre Linq que estou lendo agora: Linq in Action, dos autores Fabrice Marguerie, Steve Eichert, Jim Wooley, publicado por Manning. Eu não encontrei literatura em português para Linq, então dois grandes amigos meus (Claudão e Fernandão) me deram de presente de aniversário. Um dos melhores presentes que eu já recebi até hoje hehe. Valeu galera! Além de cobir Linq ainda mostra muito de C# e boas práticas.

Voltando à tecnologia. Esta tecnologia nos permite realizar consultas em coleções de dados em memória. instâncias do tipo array, List e outras que implementam a interface IEnumerable podem ser usados com Linq. Basta adicionar o namespace System.Linq no nosso arquivo de código que já podemos utilizar o Linq.

Linq to Objects é particularmente útil quando precisamos obter dados de uma coleção em memória e adicionar funcionalidades de filtro e/ou ordenação. Estas tarefas feitas sem o Linq requerem uma boa quantidade de código para ser implementadas usando loops e variáveis temporárias. Outra vantagem de usar o modo de queries do Linq to Objects é a maior legibilidade do código e facilidade de manutenção e depuração, visto que a instrução mostra claramente o que vamos obter como resultado. Vamos a um exemplo prático:

string[] Meses = {"Janeiro",
"Fevereiro",
"Março",
"Abril",
"Maio",
"Junho",
"Julho",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Dezembro"};

var MesesComJ =
from mes in Meses
where mes.StartsWith("J")
orderby mes descending
select mes;

foreach (var m in MesesComJ) {
Console.WriteLine(m);
}


O resultado será:

Junho
Julho
Janeiro


Como seria para gerar este mesmo resultado sem Linq? Se quiser se divertir um pouco e praticar algoritmos, fique à vontade :)

Agora imagine se, em vez de um array de strings pudéssemos fazer uma query com uma lista de objetos? E é possível:

class Program {

class Pessoa {
public string Nome { get; set; }
public string Sobrenome { get; set; }
}

static void Main(string[] args) {

var Felipe = new Pessoa { Nome = "Felipe", Sobrenome = "Guerço" };
var Gerson = new Pessoa { Nome = "Gerson", Sobrenome = "Motta" };

var Pessoas = new List<Pessoa> {Gerson, Felipe};

var qry =
from pessoa in Pessoas
orderby pessoa.Nome
select pessoa;

foreach (var p in qry) {
Console.WriteLine("{0} {1}", p.Nome, p.Sobrenome);
}
}
}


Aqui só vou desviar um pouco do Linq para mostrar novos recursos da linguagem C# para quem não conhece. Primeiro, auto propriedades:

public string Nome { get; set; }


Esta implementação cria a propriedade e, por trás das cortinas, cria os campos encapsulados para armazenar os valores das propriedades públicas. Muito útil para aquelas propriedades que são lidas e escritas diretamente nos campos, sem lógica de validação, ao mesmo tempo que deixa preparado para o futuro, sendo necessário somente terminar a implementação porque a propriedade já está definida.

Depois, tipos inferidos pelo compilador:

var Felipe = new Pessoa { Nome = "Felipe", Sobrenome = "Guerço" };


Nas versões anteriores nós tínhamos que definir o tipo da variável antes de inicializá-la. Com esta nova técnica o compilador infere o tipo da variável de acordo com o valor que é passado na inicialização. Não se engane: a definição de tipo ainda é forte, apenas nós não precisamos repetir o que a inicialização já diz. O compilador mesmo assim infere o tipo de dados correto na variável. Para variáveis declaradas sem inicialização nós ainda devemos definir o tipo previamente.

Por fim, um construtor especial:

var Felipe = new Pessoa { Nome = "Felipe", Sobrenome = "Guerço" };


Este construtor nos permite criar uma nova instância de uma classe já definindo suas propriedades.

Agora voltando ao Linq. Aqui selecionamos todas as pessoas da lista ordenando pela propriedade Nome. Se quiséssemos, poderíamos adicionar um filtro por nome adicionando uma cláusula where na consulta:

where pessoa.Nome.Contains("xxx")


Legal né. Mas ainda tem mais! Podemos juntar na mesma query coleções de dados relacionados:


class Program {

class Pessoa {
public string Nome { get; set; }
public string Sobrenome { get; set; }
}

class Pet {
public string Nome { get; set; }
public Pessoa Dono { get; set; }
}

static void Main(string[] args) {

var Felipe = new Pessoa { Nome = "Felipe", Sobrenome = "Guerço" };
var Gerson = new Pessoa { Nome = "Gerson", Sobrenome = "Motta" };

var Meg = new Pet { Nome = "Meg", Dono = Felipe };
var Rex = new Pet { Nome = "Rex", Dono = Gerson };
var Max = new Pet { Nome = "Max", Dono = Gerson };
var Nina = new Pet { Nome = "Nina", Dono = Felipe };

var Pets = new List<Pet> { Meg, Rex, Max, Nina };

var qry =
from pet in Pets
orderby pet.Dono.Nome
select new {
NomeDono = pet.Dono.Nome + ' ' + pet.Dono.Sobrenome,
NomePet = pet.Nome
};

foreach (var p in qry) {
Console.WriteLine("{0} {1}", p.NomeDono, p.NomePet);
}

}
}


Agora veremos mais um recurso interessante: Anonymous types.

select new {
NomeDono = pet.Dono.Nome + ' ' + pet.Dono.Sobrenome,
NomePet = pet.Nome
};


Aqui definimos um novo tipo (anônimo porque é definido ao mesmo tempo em que inicializamos as propriedades). Agora temos um objeto com as propriedades NomeDono e NomePet. O compilador define automaticamente os tipos de dados das propriedades de acordo com sua inicialização.

Agora vamos fazer uma pequena totalização. Vamos mostrar o dono e quantos pets ele possui:

var qry2 =
from pet in Pets
group pet by pet.Dono.Nome + ' ' + pet.Dono.Sobrenome into donos
select new {
Nome = donos.Key,
Pets = donos.Count()
};

foreach (var p in qry2) {
Console.WriteLine("Pets de {0}: {1}", p.Nome, p.Pets);
}


Agora nós criamos um grupo na query agrupando pets com a chave sendo o nome do dono. Neste grupo temos a propriedade Key, que representa o que estamos agrupando. Aí projetamos no resultado a chave do grupo e o contador de registros de cada grupo.

Antes de acabar vou fazer somente uma ressalva: Podemos usar estes resultados como fontes de dados para qualquer controle data-aware de WinForms ou WebForms (GridView, por exemplo).

Bem pessoal, acabou ficando com um tamanho legal, mas espero que tenha mostrado a quem ainda não conhecia como o Linq to Objects pode ser uma boa opção para trabalhar com dados em memória em nossas soluções. Grande abraço e até a próxima!

Um comentário:

  1. Felipão, Felipão, sensacional!!!
    Sou seu fã, arrasou!
    Parabéns amigo, belo trabalho ... transformando suor em ouro.
    Obrigado!

    ResponderExcluir