Em várias aplicações é muito comum você ter que executar uma determinada query diversas vezes. Essas queries são utilizadas por diversos pontos do sistema e por diversos usuários. Quando estamos utilizando o LINQ To SQL ou o Entity Framework, a cada query que fazemos em C#/VB.NET, há uma série de procedimentos internos que são realizados para transformar/traduzir a query em T-SQL (ASTs).
Para evitar que esses procedimentos aconteçam a todo o momento, podemos recorrer a uma classe chamada CompiledQuery. Basicamente, a finalidade desta classe é otimizar a execução, que apenas efetua todos os procedimentos uma única vez, e faz o caching do “plano de execução” (em termos de entidades). E para fazer tudo isso funcionar, basta utilizar o método Compile, que esta classe disponibiliza. Esse método retorna um delegate do tipo Func<,,>, representando a query compilada, que por sua vez, especificará os tipos dos parâmetros necessários para ela, tais como, a fonte de dados (DataContext ou ObjectContext), os parâmetros de entrada e o resultado.
Vale lembrar que essa classe não é comum para ambas as tecnologias, ou seja, há uma versão dela para o Linq To SQL e outra para o Entity Framework, contidas nos namespaces System.Data.Linq e System.Data.Objects, respectivamente. O exemplo abaixo ilustra uma classe chamada DBQueries, que possui um membro estático chamado AreasDeUmaEmpresa, que representa uma query frequentemente utilizada em um determinada aplicação, e que faz uso do Linq To SQL como acesso a dados:
using System.Linq;
using System.Data.Linq;
internal static class DBQueries
{
public static Func<DBContext, int, IEnumerable<Area>> AreasDeUmaEmpresa;
static DBQueries()
{
AreasDeUmaEmpresa =
CompiledQuery.Compile<DBContext, int, IEnumerable<Area>>(
(db, empresaId) => from a in db.Areas where a.EmpresaID == empresaId select a);
}
}
Abaixo está a mesma query, mas com uma ligeira modificação (não muito vísivel) para que funcione com o Entity Framework:
using System.Linq;
using System.Data.Objects;
internal static class DBQueries
{
public static Func<DBEntities, int, IEnumerable<Area>> AreasDeUmaEmpresa;
static DBQueries()
{
AreasDeUmaEmpresa =
CompiledQuery.Compile<DBEntities, int, IEnumerable<Area>>(
(db, empresaId) => from a in db.Areas where a.EmpresaID == empresaId select a);
}
}
Como já sabemos, apesar de terem o mesmo nome, as classes CompiledQuery são tipos diferentes. O método Compile de cada uma delas possui uma constraint para o primeiro tipo genérico (TArg0), que identifica a fonte de dados, ou melhor, o contexto. No caso do LINQ To SQL, ele nos obrigará a definir esse primeiro parâmetro com um tipo que herde direta ou indiretamente da classe DataContext, enquanto a classe CompiledQuery utilizada pelo Entity Framework, determina que este mesmo parâmetro deve ser uma classe do tipo ObjectContext.
Em ambos os casos, armazenamos o resultado gerado pelo método Compile das respectivas classes CompiledQuery, em campos estáticos. Esse campo foi inicializado no construtor (também estático), mas talvez você possa fazer isso sob demanda. A idéia é criar um membro estático para armazenar cada query compilada, mantendo o resultado durante a execução da aplicação, reutilizando-a quando necessário.
Abaixo consta um exemplo de utilização da query compilada com LINQ To SQL. O Entity Framework seguirá a mesma idéia, apenas criando o contexto correspondente.
using (DBContext db = new DBContext())
foreach (var item in DBQueries.AreasDeUmaEmpresa(db, 12)) { }
Queries compiladas tem uma performance consideravelmente melhor em relação as queries não compiladas, e usá-las poderá trazer bons ganhos de performance, podendo inclusive, utilizá-la em conjunto com o ConnectedDataContext. Mas também há um ponto negativo: você não conseguirá, de forma simples, projetar possíveis tipos anônimos que possam ser gerados através do select. Como tipos anônimos não podem ser propagados entre chamadas de métodos, então a única opção que nos resta é a criação de uma classe para armazenar e servir como wrapper para esse resultado.