DataSet x DataReader : Uma questão de desempenho ?


Quem é que não gosta de fazer o menor esforço quando precisa codificar. Quanto se trata de acessar dados com VB .NET ou ASP .NET temos duas opções claras : DataSet e DataReader. E em se falando de pouco esforço quanto a codificação , com o DataSet podemos fazer o acesso basicamente com 3 linhas de código. Basta preencher o DataSet e a seguir fazer a interação usando um loop.

Mas e quanto ao desempenho ? Geralmente o desempenho é um requisito que vem em primeiro lugar , quer em aplicações Windows Forms quer em aplicações Web.

Quando se fala em desempenho a utilização de um DataReader leva vantagem sobre um DataSet. Com um DataReader você tem acesso aos dados assim que o objeto fica disponível e não necessita esperar , como no caso do DataSet , o seu preenchimento.

As vantagens do DataReader

A seguir temos um código que preenche um DataSet com o resultado de uma tabela e exibe o primeiro campo em cada linha:0

 SqlConnection conn = new SqlConnection(connectionString);
 SqlDataAdapter da = new SqlDataAdapter ("select * from tabela;",conn);

 DataSet ds = new DataSet();

 da.Fill(ds);

 foreach (DataRow dr in ds.Tables[0].Rows)
 {
     Console.WriteLine(dr[0].ToString());
 }

Dim conn As New SqlConnection(connectionString)
Dim da As New SqlDataAdapter("select * from tabela;", conn)

Dim ds As New DataSet()
da.Fill(ds)

Dim dr As DataRow
For Each dr In ds.Tables(0).Rows
    Console.WriteLine(dr(0).ToString())
Next dr
C# VB.NET

Observando o código você pode notar que a inspeção dos dados feita no loop foreach começa somente após o DataSet ter sido preenchido. Não dá para realizar outra tarefa enquanto o DataSet estiver sendo preenchido.

Vamos ver agora o código que realiza a mesma tarefa usando um DataReader:

 SqlConnection con = new SqlConnection(connectionString);
 SqlCommand cmd = new SqlCommand("select * from tabela", con);

 cmd.Connection.Open();

 SqlDataReader dr =   cmd.ExecuteReader(CommandBehavior.CloseConnection);

 while(dr.Read())  
 {
   Console.WriteLine(dr.GetString(0));
 }

 dr.Close();
 con.Close();

 
Dim con As New SqlConnection(connectionString)
Dim cmd As New SqlCommand("select * from tabela", con)

cmd.Connection.Open()

Dim dr As SqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection)

While dr.Read()
   Console.WriteLine(dr.GetString(0))
End While

dr.Close()
con.Close(
)
C# VB .NET

Neste caso a inspeção dos dados é feita tão logo os dados estiverem disponíveis através de um loop While , onde dr.Read() retorna false se não encontrar dados. O DataReader armazena um resultado por vez no cliente , e , com isto reduz o uso de memória e recursos do sistema.

É óbvio que dependendo da situação somente um DataSet irá resolver a questão : serializar o resultado ou passar uma consulta para a próxima camada da sua aplicação , vai requerer uma coleção e um DataSet fornece um modelo adequado para estas tarefas.

Na maioria das consultas empregadas nas aplicações Web , onde os dados é pesquisado , exibido e então descartado um DataReader irá aumentar o desempenho da sua aplicação de forma significativa.

Então usar um DataReader é mais rápido que usar um DataSet . Certo ?   Bem , sim e depende...

Será que não existe um modo de otimizar o desempenho de um DataSet para justificar sua utilização ?

Sim , existe e eu vou mostrar como você pode fazer isto. Antes vou mostrar o porque você quereria usar um DataSet.

Os DataSet possuem um conjunto de funcionalidades que os DataReaders não possuem , dentre eles destacamos:

Escolher usar um DataReader somente olhando a questão do desempenho irá fazer com que você não tenha acesso a estas funcionalidades.

Existem outros aspectos negativos em um DataReader que talvez o façam mudar de idéia.  Em sistemas multiusuários , enquanto  os DataSets/DataAdapters liberam suas conexões e bloqueios assim que o preenchimento via método Fill termina , os DataReaders estão gerenciando a conexão e qualquer bloqueio aberto durante todo o tempo que durar o loop de inspeção de dados. (dr.Read()). Isto pode levar a uma maior contenção do seu banco de dados e com isto diminuir o desempenho .

A única justificativa para usar um DataSet seria então melhorar seu desempenho.

Melhorando o desempenho de um DataAdapter

Você pode aproximar o desempenho de um DataAdapter de um DataReader desativando temporariamente algumas das funcionalidades avançadas padrão de um DataSet durante o processamento. Creio que os pontos chave em matéria de desempenho durante o processamento de um DataSet são a integridade referencial e a indexação que o DataSet usa para manter os dados internamente.

Abaixo temos um código que mostra o preenchimento de um Dataset , via DataAdapter , que possui duas tabelas do banco de dados Northwind.mdb  : Customers e Employees

DataSet ds= new DataSet();

string strConn="Server=(local);dataBase=Northwind;user id=sa;password=;";

SqlConnection cn = new SqlConnection(strConn);

string strSQL="select * from Customers;select * from employees";

cn.Open();

SqlDataAdapter da = new SqlDataAdapter( strSQL,cn);

ds.Tables.Add("Customers");
ds.Tables.Add("Employees");

ds.EnforceConstraints =false;

ds.Tables["Customers"].BeginLoadData();
da.Fill(ds.Tables["Customers"]);
ds.Tables["Customers"].EndLoadData();

ds.Tables["Employees"].BeginLoadData();
da.Fill(ds.Tables["Employees"]);
ds.Tables["Employees"].EndLoadData();

dataGrid1.DataSource=ds.Tables["Customers"];
dataGrid2.DataSource=ds.Tables["Employees"];

cn.Close();

Neste código tomamos algumas medidas para melhorar o desempenho do DataSet. Vejamos quais:

A primeira delas foi definir a propriedade - EnforceConstraints - como false ; isto desabilita a verificação das restrições durante a operação e pode tornar a operação mais rápida. Você pode voltar a definir o valor como True depois que os dados forem retornados dentro um loop try/Catch e tratando a exceção ConstraintException.

Outro fator que onera o desempenho de um DataSet é o estabelecimento de uma chave primária. Podemos também desabilitar temporariamente a indexação e notificação interna. Para isto fazemos o seguinte:

1- Executamos o método BeginLoadData antes de usar o método Fill para desabilitar a notificação , indexação.
2- Executamos o método EndLoadData depois de usar o método Fill para habilitar a indexação a notificação.

Estes métodos são membros da classe DataTable e por isso você vai precisar chamá-los para a DataTable particular que você esta preenchendo.

Com isto melhoramos o desempenho do DataSet e com isto justificamos sua utilização afim de podermos usar seus recursos.

Aguarde mais artigos sobre ADO.NET...


José Carlos Macoratti