LINQ TO SQL - Turbo Web Apps usando Compiled Queries

Para muitos não é novidade que podemos atingir ganhos de performance muito superiores utilizando as compiled queries em LINQ. Claro que isso não é verdade para todos e espero que para todos esses que estejam a ler este Post possam aprender algo,

Antes de mais gostava de explicar o porquê de estar a escrever sobre este tópico. Esta semana decidi dar uma olhadela na performance das páginas do trokas, uma vez que tenho estado a implementar novas funcionalidades, também aproveitei para fazer alguns cleanups (tanto de visual, como de código). Ao que decidi olhar também para o tempo de render das minhas página. Ora como portal que é o trokas tem dezenas de ligações à base de dados por página e eu que algures no tempo achei tão fixe a possibilidade de usar LINQ para não mais ter trabalho de escrever as consultas de SQL à mão, nunca mais olhei para a performance convencido de que o LINQ TO SQL optimizava toda aquela confusão que eu largava para ali.

Ao olhar então para o tempo de render de uma determinada página observei que os tempos de render variavam entre os 0,5 segundos e os muitos segundos (desvantagens de um servidor partilhado). Até que me lembrei mas espera lá quando comecei o projecto as páginas demoravam menos de 0,1'' para fazer o render. Após vários testes (ainda em curso diga-se de passagem), cheguei à conclusão que os tempos excessivos deviam-se às ligações à base de dados. E como qualquer rookie lembrei-me que se calhar até era fixe ter um único contexto de ligação à base de dados estático pois talvez assim tivesse uma ligação sempre activa e não fosse penalizado pelas aberturas e fechos de ligações. Wronnggg. ok depois de muiiitos testes e muitas reviravoltas acabei por perceber que simplesmente não é viável devido ao caching interno que é feito pelos objectos de LINQ.

À segunda tentativa lembrei-me de fazer testes com Stored Procedures e voilá que coisa tão rápida que até parecia mentira. Já estava todo contente e lixado ao mesmo tempo. Do género "no more linq to sql for me" (mais ou menos estava a colocar as stored procedures no DBML).
Claro que a história não ficou por aqui e então decidi pesquisar mais um pouco sobre como melhorar a performance das consultas de LINQ. Et voilá. Afinal existe uma coisa fixe, porreira, e até tem um nome porreiro, Compiled Queries. Ou seja, posso compilar as queries que são o verdadeiro terror da performance? Sim. Mas não há bela sem senão.

As compiled Queries

Uma Consulta Compilada em Linq básicamente servem para tirar o overhead de recompilar uma consulta em cada pedido ao servidor. Para isso  é necessário criar um método estático na nossa classe (ou de preferência numa classe separada para manter o código mais limpo) utilizando o tipo FUNC.

A sintaxe é a seguinte:

 

public static Func<MySiteDataContext, int, Artigo> GetArtigo=

            CompiledQuery.Compile((MySiteDataContext db, int artigoId) =>

                (from a in db.Artigos

                 where a.artigoId == artigoId

                 select a).FirstOrDefault()

                );

Neste caso que apresentei só retorna um artigo e como tal estamos a dizer na declaração que o tipo a devolver é um Artigo, mas fácilmente podemos devolver um iQueryable do tipo artigo.

Mas claro que isto é muito bonito só que vem logo outra bomba atrás... Mas e os tipos anónimos? Também tem solução. Um pouco mais de trabalho mas tem solução.

As Compiled Queries que devolvem um tipo anónimo

Agora já devem estar a pensar isto é uma brisa nem dá trabalho nenhum. Mas deparem-se com a necessidade de usar tipos anónimos e digam lá que não desejavam não ser programadores? Ok sim desejavam.
Mas infelizmente para devolver uma compiled query com um tipo anónimo só dá se a usarmos no contexto em que a estamos a aplicar (o que deixa de ter sentido pois não beneficiamos de uma consulta compilada em todos os pedidos), ou temos de criar o tipo de objecto a ser devolvido para podermos indicar o mesmo. Assim sendo criamos primeiro uma classe com as propriedades que devem ser devolvidas pelo que seria o tipo anónimo. Por exemplo:

public sealed class NovoTipoDeArtigo
{
      public int artigoId {get;set;}
      public string artigo {get;set;} 
      public string custoDoArtigoComIva{get;set;}

E agora já podemos dizer ao Delegate qual o tipo de objecto a devolver:

 

public static Func<MySiteDataContext, int, iQueryable<NovoTipoDeArtigo>> GetArtigo=

            CompiledQuery.Compile<MySiteDataContext, int, iQueryable<NovoTipoDeArtigo>>((MySiteDataContext db, int artigoId) =>

                from a in db.Artigos

                 where a.artigoId == artigoId

                 select new NovoTipoDeArtigo {
                        artigoId = a.artigoId,
                        artigo = a.artigo,
                        custoDoArtigoComIva = a.preço * db.Impostos.Where(x=> x.imposto == "IVA").FirstOrDefault().valor, 

                 }

              );

Para quem teve paciência para ler isto tudo até ao fim e até percebeu mais ou menos a pessegada que eu coloquei aqui, aposto que devem querer saber qual o ganho a nível de performance. Pois é, então o primeiro benefício é que a mesma página (que por sinal ainda não acabei de optimizar), já está a fazer um render mínimo de 0,07 segundos o que comparado com os 0,5 segundos e tal iniciais, equivale a algo como um ganho de muito mais de 200%.

 

 

Leave a Comment

(requerido) 
(requerido) 
(opcional)
(requerido) 
If you can't read this number refresh your screen
Enter the numbers above: