LINQ - Pré-compilando consultas para ganhar desempenho


Se o termo LINQ é novo para você recomendo que acesse a seção do site onde vai encontrar diversos artigos apresentando e usando a tecnologia LINQ.

Hoje vou falar sobre um recurso que você pode usar para melhorar o desempenho das suas consultas LINQ: A pré-compilação.

Este artigo baseia-se em uma tradução literal do artigo - Pre-compiling Queries for Performance (Doug Rothaus);

Sabe aquelas consultas complexas que você usa repetidas vezes ? Elas são as candidatas preferidas a utilização desse recurso.

Na pré-compilação de uma consulta LINQ é feito um processamento para compilar a consulta um única vez e na próxima execução ela já estará compilada e pronta para ser executada.

Para criar consultas pré-compiladas você usa o método Compile da classe CompiledQuery do namespace System.Data.Linq.

Na prática você passa uma expressão lambada que contém as variáveis usadas pela consulta, o objeto DataContext, as variáveis , etc. para o método Compile para criar a consulta compilada. Veja abaixo um exemplo :

Dim query As Func(Of NorthwindDataContext, Customer, IQueryable(Of Order)) = _
    System.Data.Linq.CompiledQuery.Compile( _
       Function(database As NorthwindDataContext, cust As Customer) _
           From order In database.Orders _
           Where order.Customer Is cust) 

Na primeira vez que a consulta for executada, ela será compilada e armazenada em uma variável especificada. Depois disso, a consulta compilada será usada quando a consulta for executada novamente.

Vamos dar um exemplo básico com LINQ usando este recurso criando uma aplicação Windows Forms que acessa o banco de dados Northwind.mdf e exibe uma relação de clientes do banco de dados Northwind usando uma consulta LINQ e uma lista de detalhes de seus pedidos para o cliente selecionado usando uma consulta LINQ pré-compilada.

Vamos ao trabalho...

Abra o Visual Basic 2008 Express Edition e crie uma aplicação Windows Forms a partir do menu Arquivo->Novo Projeto;

Opa ! O meu VB 2008 Express esta em português !!! Quer traduzir o seu ??? Se quiser baixe o pacote de tradução no sítio:

http://www.microsoft.com/downloads/details.aspx?FamilyId=BF3CB4CB-4C4D-43BF-90AB-652BFA9EEC5B&displaylang=pt-br

Veja a aparência do projeto no VB 2008 Express traduzido:

A seguir uma dica do colega:

Eu consegui colocar o VS2008 Professional Edition em português (não testei se funciona no Team System, mas provavelmente sim).

O que eu fiz instalei o SQL Server 2008 Express Editon (Em português). http://www.microsoft.com/express/sql/download/

Depois instale o VS2008, automaticamente o VS2008 ficou em português.

Mas caso não queira que fique em português mais basta ir no menu -> Ferramenta -> opções, Em Ambiente ->  Configurações internacionais e na parte direita escolha o idioma desejada English ou Português.

A seguir a partir do menu Projeto->Adicionar Novo Item e na janela Modelos selecione LINQ to SQL Classes, altere o nome para Northwind.dbml e clique em Adicionar;

O Descritor Objeto relacional LINQ será exibido vazio. Vamos então definir o mapeamento O/RM usando LINQ.

Visualize a janela Gerenciador de banco de dados e localize a conexão com o banco de dados Northwind.mdf . (se você não tem uma conexão criada com o Northwind.mdf você deverá criá-la.)

Expanda os objetos do banco de dados e arraste a tabela Customers e Orders para o Descritor OR/M LINQ;

Selecione o formulário na janela Gerenciador de Soluções e inclua os componentes:

Monte o leiaute do formulário conforme a figura abaixo:

Vamos agora código necessário para exibir os dados:

Declarando o import usado no projeto:

Imports System.Data.Linq

Definindo as variáveis , a expressão lambda e instanciando um objeto StopWatch

'definindo a expressÆo lambda
Private consultaPedido As Func(Of NorthwindDataContext, Customer, IQueryable(Of Order))

' O objeto DataContext do LINQ to SQL para o Northwind.
Private db As New NorthwindDataContext

' Define as vari veis que vÆo medir o tempo para exibir a diferen‡a de desempenho
Dim tempoConsulta As New Stopwatch
Dim
tempoLento As Long?
Dim
tempoAtual As
Long
 

Nota:

A interface IQueryable fornece a funcionalidade para avaliar consultas em uma fonte de dados específica no qual o tipo de dados é conhecido.

A interface IQueryable<(Of <(T>)>) se destina a implementação por provedores de consulta.

Essa interface herda a interface IEnumerable<(Of <(T>)>) de forma que se ela representa uma consulta , o resultado da consulta pode ser enumerado.

Consultas que não retornam resultados enumeráveis são executadas quando o método Execute<(Of <(TResult>)>)(Expression) é chamado.

Agora vamos definir o método getPedidos() que irá retornar os pedidos de um cliente usando uma consulta pré-compilada:

Private Function getPedidos(ByVal selectedCustomer As Customer) As List(Of Order)
 

' Compila a consulta para pedidos se ela nÆo foi compilada ainda.

' Caso contr rio usa a consulta compilada


If
consultaPedido Is Nothing
Then


    consultaPedido = CompiledQuery.Compile( _

                             Function(database As NorthwindDataContext, clientes As Customer) _

                             From pedidos In database.Orders _

                             Where pedidos.Customer Is clientes)

End If
 

' Executa a consulta compilada chamando o m‚todo ToList method e retornando o resultado

Return consultaPedido(db, selectedCustomer).ToList()


End
Function

No evento Load do formulário vamos definir o seguinte código que vai preencher o ListBox com os dados da tabela Clientes;

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 

' Obtem a lista de clientes e vincula no ListBox1.

ListBox1.DataSource = From clientes In db.Customers _

                                Select clientes _

                                Order By clientes.CompanyName


                               'exibe o campo CompanyName no ListBox

                               ListBox1.DisplayMember = "CompanyName"
 

End Sub

Agora devemos implementar o código a seguir no evento SelectedIndexChanged   do ListBox de forma que quando um cliente for selecionado os seus pedidos sejam buscados e exibidos. Além disso iremos calcular o tempo gasto nesta operação e exibir no controle Label;

Ao selecionar um cliente o evento irá passar o cliente atual selecionado para o método gePedidos() para chamar a consulta pré-compiada e retornar os pedidos do cliente.

Os tempos exibidos referem-se ao tempo atual gasto para executar a consulta pré-compilada e o tempo gasto na primeira execução quando a consulta não estava pré-compilada.

Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged


tempoConsulta.Reset()

tempoConsulta.Start()


' Se um cliente foi selecionado , obtem a lista de pedidos para o cliente

' e vincula no DataGridView1.

If ListBox1.SelectedValue IsNot Nothing Then

    DataGridView1.DataSource = getPedidos(ListBox1.SelectedValue)

End If
 

tempoConsulta.Stop()

tempoAtual = tempoConsulta.ElapsedMilliseconds

If tempoLento Is Nothing OrElse tempoAtual > tempoLento Then

   tempoLento = tempoAtual

End If

Label1.Text = "Tempo gasto atual : " & tempoAtual & " # tempo primeira execu‡Æo: " & tempoLento

End Sub

Nota:

O objeto Stopwatch  é muito útil para calcular o tempo que foi gasto entre dois intervalos de tempo em uma aplicação VB .NET.

Para usar o objeto Stopwatch   você precisa chamar o seu método Start para iniciar a contagem do tempo; o método Stop interrompe a contagem. O método Reset é útil para limpar a contagem do tempo iniciando o contador.
 

Executando o projeto iremos obter:

Observe que o tempo gasto na consulta pré-compilada é bem menor que na primeira execução.

Pegue o projeto completo aqui : LinqConsultasCompiladas.zip

Eu sei é apenas LINQ mas eu gosto...

Referências:


José Carlos Macoratti