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:
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...
José Carlos Macoratti