LINQ To SQL - Dicas para otimização do desempenho


O LINQ To SQL veio chegando devagar e pretende com o tempo ocupar o seu lugar no acesso a dados quando existir a necessidade de se efetuar o mapeamento objeto relacional.

Nota: Se você ainda não sabe nada sobre LINQ sugiro que leia os meus artigos na seção : LINQ

Com o amadurecimento da tecnologia sua utilização tende a aumentar e com isso devemos estar atentos para evitar erros que possam comprometer o desempenho da nossa aplicação.

O LINQ to SQL oferece muitos métodos e propriedades que podemos usar para otimizar o seu desempenho.

Com isso em mente eu compilei algumas dicas para otimizar o utilização do LINQ to SQL. Vamos a elas:

1 – Desabilite a propriedade ObjectTrackingEnabled do Data Context se ela não for necessária

A propriedade ObjectTrackingEnabled orienta o Framework a controlar o valor original e a identidade do objeto para o DataContext.

A definição dessa propriedade como false melhora o desempenho ao tempo de recuperação, porque há menos itens para controlar.

Então se você esta apenas retornando dados como somente leitura e não pretende efetuar nenhuma alteração você não precisa gerenciar a identidade dos objetos para o DataContext. Com isso o DataContext não precisa armazenar os objetos visto que eles não serão alterados.

Você pode desabilitar esta propriedade da seguinte forma:

using (NorthwindDataContext context = new NorthwindDataContext())
{
context.ObjectTrackingEnabled = false;
}
Using context As New NorthwindDataContext()
  context.ObjectTrackingEnabled = False
End Using
C# VB .NET

2 – Não jogue todos os seus objetos de banco de dados em um único DataContext

O DataContext representa um única unidade de trabalho e não todo o seu banco de dados, certo !!!. Se você possui diversos objetos de banco de dados que não estão conectados ou eles não são utilizados (tabelas de logs, objetos usados para processamento em lote, etc.) eles irão consumir espaço na memória aumentando o gerenciamento de identidade de objetos degradando o desempenho.

Procure planejar a separação da sua área de trabalho em mais de um DataContext onde cada um represente uma única unidade de trabalho associada. Você pode também configurar os DataContext para usar a mesma conexão para usar os recursos do pooling de conexões.

3 – Utilize  CompiledQuery sempre que for necessário

A classe CompiledQuery é usada para compilação e cache de consultas para reutilização.

Quando você estiver criando e executando sua consulta , existem diversas etapas para gerar o SQL apropriado a partir da sua expressão dentre os quais os mais importantes são:

  1. Cria a árvore de expressões;
  2. Converte a expressão para SQL;
  3. Roda a consulta;
  4. Retorna os dados;
  5. Converte os dados para objetos;

Como você pode notar, quando você esta usando a mesma consulta muitas vezes, os passos 1 e 2 estão sendo repetidos e consumindo tempo. Usando o método Compile da classe CompiledQuery você compila a sua consulta uma vez e a armazena para ser usada muitas vezes. Vejamos um exemplo:

Dim func As Func(Of NorthwindDataContext, IEnumerable(Of Category)) = CompiledQuery.Compile(Of NorthwindDataContext,                                                                                                            IEnumerable(Of Category))(Function(ByVal context As                                                                                                            NorthwindDataContext) context.Categories.Where(Of                                                                                                            Category)(Function(cat) cat.Products.Count > 5)) VB .NET
Func<NorthwindDataContext, IEnumerable<Category>> func =CompiledQuery.Compile<NorthwindDataContext,
                                                                                        IEnumerable<Category>>((NorthwindDataContext context) =>                                                                                         context.Categories.Where<Category>(cat => cat.Products.Count > 5));
C#

Estamos usando CompiledQuery.Compily e retornando em “func” a consulta compilada que será compilada uma única vez . Após isso podemos armazenar a consulta compilada em uma classe estática:

''' <summary>
''
' Classe para armazenar consultas compiladas
''' </summary>
Public Module utilitarioLINQ

   Private Sub New()
    End Sub
'
'' <summary>
''' Obtem uma consulta que retorna categorias com mais de 5 produtos
''' </summary>
''' <value>A consulta contendo a categoria com mais de 5 produtos.</value>

Public ReadOnly Property getCategorias() As Func(Of NorthwindDataContext, IEnumerable(Of Category))
    Get
         Dim func As Func(Of NorthwindDataContext, IEnumerable(Of Category)) = CompiledQuery.Compile(Of NorthwindDataContext,
         IEnumerable(Of Category))(Function(ByVal context As NorthwindDataContext)
         context.Categories.Where(Of Category)(Function(cat) cat.Products.Count > 5))
    Return func
End Get
End Property
End Module
VB .NET
/// <summary>
' Classe para armazenar consultas compiladas
/// </summary>
public static class UtilitarioLINQ
{
  '' <summary> 
''' Obtem uma consulta que retorna categorias com mais de 5 produtos
''' </summary> 
''' <value>A consulta contendo a categoria com mais de 5 produtos.</value> 
  public static Func<NorthwindDataContext, IEnumerable<Category>>
    GetCategories
      {
      get
      {
        Func<NorthwindDataContext, IEnumerable<Category>> func =
          CompiledQuery.Compile<NorthwindDataContext, IEnumerable<Category>>
          ((NorthwindDataContext context) => context.Categories.
            Where<Category>(cat => cat.Products.Count > 5));
        return func;
      }
    }
}
C#

A seguir podemos usar esta consulta compilada de maneira bem simples:

Using context As New NorthwindDataContext()
       utilitarioLINQ.GetCategories(context)
End Using
VB .NET
using (NorthwindDataContext context = new NorthwindDataContext())
{
        utilitarioLINQ.GetCategories(context);
}
C#

Armazenando e usando a consulta desta forma estamos ganhando desempenho.

4 – Filtre os dados quando necessário usando DataLoadOptions.AssociateWith

Quando você for retornar dados usando Load ou LoadWith esta assumindo que deseja retornar todos os dados associados que estão vinculados a chave primária (o id do objeto). Mas em muitas situações você poderia usar um filtro para realizar tal tarefa. É nesta situação que o método genérico DataLoadOptions.AssociatedWith pode lhe ajudar.

Este método utiliza um critério para carregar os dados como um parâmetro e aplica este critério à consulta de forma que você irá obter somente os dados que você realmente deseja.

O exemplo abaixo associa e retorna todas as categorias que possuem somente produtos que não foram descontinuados:

Using context As New NorthwindDataContext()

Dim options As New DataLoadOptions() options.AssociateWith(Of Category)(Function(cat) cat.Products.Where(Of Product)(Function(prod) Not prod.Discontinued))
context.LoadOptions = options
End Using
VB.NET
using (NorthwindDataContext context = new NorthwindDataContext())
{
DataLoadOptions options = new DataLoadOptions();
options.AssociateWith<Category>(cat=> cat.Products.Where<Product>(prod => !prod.Discontinued));
context.LoadOptions = options;
}
C#

5 – Desabilite a concorrência otimista se você não precisar o recurso

A enumeração UpdateCheck especifica quando objetos são testados para verificação de conflitos de concorrência.  A enumeração pode assumir os seguintes valores:

Nome do membro Descrição
Always Sempre verifique.
Never Nunca Marcar.
WhenChanged Marque somente quando o objeto foi alterado.

O LINQ to SQL já vem com o suporte da Concorrência Otimista para colunas timestamp que são mapeadas para o tipo Binary. Você pode habilitar ou desabilitar este recurso no mapeamento do arquivo e atributos para as propriedades. Se sua aplicação pode rodar sem usar este recurso.

A enumeração UpdateCheck.Never é usada para desabilitar a concorrência no LINQ to SQL.

A seguir temos um exemplo onde desabilitamos a concorrência otimista :

<Column(Storage := _Description, DbType := NText, UpdateCheck := UpdateCheck.Never)> _
Public Property Description() As String
Get
    Return Me._Description
End Get
Set(ByVal value As String)
If (Me._Description <> value) Then
        Me.OnDescriptionChanging(value)
        Me.SendPropertyChanging()
        Me._Description = value
        Me.SendPropertyChanged(Description)
        Me.OnDescriptionChanged()
End If
End Set
End Property
VB.NET
[Column(Storage=“_Description”, DbType=“NText”,UpdateCheck=UpdateCheck.Never)]
public string Description
{
get
{
    return this._Description;
}
set
{
if ((this._Description != value))
{
    this.OnDescriptionChanging(value);
    this.SendPropertyChanging();
    this._Description = value;
    this.SendPropertyChanged(“Description”);
    this.OnDescriptionChanged();
}
}
}
C#

6 – Monitore as consultas geradas pelo DataContext e analize os dados que estão sendo retornados

Como a consulta LINQ é gerada em tempo de execução, pode ocorrer que existam colunas adicionais ou dados a mais sendo retornados. Use a propriedade Log do DataContext para poder visualizar qual SQL esta sendo executado pelo seu DataContext. Exemplo:

Using context As New NorthwindDataContext()
     context.Log = Console.Out
End Using
Using context As New NorthwindDataContext()
    context.Log = Console.Out
End Using

Usando este código enquanto debuga  a sua aplicação você poderá ver a instrução SQL gerada na janela OutPut do Visual Studio e analisar a possibilidade de melhorar o seu desempenho.

7 – Retorne apenas o número de registro que vai utilizar

Quando você vincula dados a um  grid e efetua a paginação considere utilizar os métodos que o LINQ to SQL oferece. Estou falando dos métodos Take e Skip. Abaixo temos um exemplo que retorna os produtos para uma paginação em um controle ListView:

''' <summary>
''' Obtem produtos por página
''' </summary>
'''  <param name=”startingPageIndex”>Indice da página inicial.</param>
''' <param name=”pageSize”>Tamanho da página</param>
''' <returns>A lista de produtos na pagina</returns>

Private Function GetProducts(ByVal startingPageIndex As Integer, ByVal pageSize As Integer) As IList(Of Product)
Using context As New NorthwindDataContext()
Return context.Products.Take(Of Product)(pageSize).Skip(Of Product)(startingPageIndex * pageSize).ToList(Of Product)()
End Using
End Function
VB .NET
// <summary>
//'' Obtem produtos por página
//'' </summary>
//'' <param name=”startingPageIndex”>Indice da página inicial.</param>
//'' <param name=”pageSize”>Tamanho da página</param>
//<returns>A lista de produtos na pagina</returns>

private IList<Product> GetProducts(int startingPageIndex, int pageSize)
{
using (NorthwindDataContext context = new NorthwindDataContext())
{
return context.Products
.Take<Product>(pageSize)
.Skip<Product>(startingPageIndex * pageSize)
.ToList<Product>();
}
}
C#
 

É óbvio que não esgotei as possibilidades que podemos usar para otimizar a utilização do LINQ To SQL, apenas dei uma pequena introdução ao assunto.

Aguarde em breve mais artigos sobre otimização do LINQ to SQL.

Eu sei é apenas LINQ , mas eu gosto...

  • VB 2008 - Apresentando LINQ - Language Integrated Query
  • VB 2008 - Apresentando LINQ To SQL
  • Usando LINQ To SQL


  • José Carlos Macoratti