Sunteți pe pagina 1din 6

Explore o DataReader: por Israel Aéce Página 1 de 6

Clique aqui para instalar o Silverlight Brasil Alterar | Todos os sites da Microsoft

Pesquisa Microsoft.com Enviar Consulta

Home | Developer Centers | Biblioteca | Downloads | Assinaturas MSDN


Pesquisa rápida
Developer Centers > Visual Basic

MSDN Ir Explore o DataReader


por Israel Aéce
Carreira
Comunidade
Para ler todas as matérias da MSDN Magazine, assine a revista no endereço www.neoficio.com.br/msdn
Migração de Tecnologias
Developer Centers Este artigo discute Este artigo usa as seguintes tecnologias:
Tecnologias & Produtos
Visual Basic .NET e Visual C# .NET
Eventos MSDN • Acesso a Dados
Treinamento Download:

Assinaturas MSDN
• Utilização do objeto DataReader fontesDataReader.zip (34kB)

Avaliação de Software
• Características do DataReader
Fórum MSDN
Media Center • Performance e Boas Práticas
MSDN Flash
Dev Excellence Award Chapéu
DataReader
MSDN Magazine
Suporte Técnico
Sobre o MSDN
Há aplicações as quais necessitam dos dados sempre em tempo real, apresentando o conteúdo mais atualizado possível ao usuário. Muitos clientes
Meu MSDN
necessitam ainda de uma forma rápida para diminuir o tempo de espera de uma determinada solicitação.

Tendo este cenário, temos a meta de desenvolvermos um software que seja bastante eficiente na busca e exibição de dados ao cliente. Este artigo
explicará como e quais as melhores práticas, tanto para resgatar dados da base de dados quanto para manipular e exibí-los.

A Plataforma .NET fornece um objeto chamado DataReader, o qual está contido dentro do Namespace System.Data e que por sua vez tem como
finalidade resgatar os dados da base de dados de forma extremamente rápida. Como o DataReader é uma espécie de cursor e "caminha" somente para
frente, seus dados são somente para leitura, não sendo possível fazermos nada mais com estes dados a não ser exibi-los ao usuário final. Em uma
linguagem mais técnica dizemos que o DataReader é foward-only (somente avança) e read-only (somente leitura).

A Interface IDataReader
Esta é a Interface base que deve ser implementanda quando um novo DataReader é criado. Ele contém 3 propriedades (veja a Tabela 1) e 4 métodos
(veja a Tabela 2) que devem ser implementados nas classes que implementar esta Interface.

Tabela 1 - Propriedades da Interface IDataReader

Depth Retorna um valor maior que zero (0) caso existam linhas (rows). No caso do SQL Server, sempre será retornado 0, já que o mesmo
não suporta esta funcionalidade

HasRows * Esta propriedade (ReadOnly) retorna True se existe um ou mais registros, e False caso o DataReader não retorne nenhum registro

IsClosed Verifica se o leitor está aberto ou fechado

RecordsAffected Retorna o número de linhas modificadas, inseridas ou excluídas através da execução de SQL Statements

* A propriedade HasRows foi introduzida apenas na versão 1.1 do .NET Framework. Como as Interfaces são imutáveis, essa propriedade foi criada e
implementada nas classes que implementam IDataReader, fazendo assim parte da classe e não da Interface.

Tabela 2 - Métodos da Interface IDataReader

Close Fecha o objeto IdataReader

GetSchemaTable Retorna um objeto do tipo DataTable contendo as descrições das colunas contidas no DataReader

NextResult Utilizado para processar múltiplos SQL Statements. Retorna True caso encontre um Statements e False caso contrário

Read Avança o IDataReader para o próximo registro. O método retorna um valor Booeano indicando True se existem mais linhas e False
caso contrário. Faz-se necessário chamar o este método para que seja possível ler os registros.

Para cada Provider temos um objeto DataReader específico (xxxDataReader) e cada um destes DataReaders implementam a interface IDataReader. As
classes são as seguintes: OdbcDataReader, OleDbDataReader, OracleDataReader, SqlCeDataReader e SqlDataReader. Cada uma destas classes
implementam os métodos e propriedades da interface IDataReader de acordo com o seu Provider, pois a implementação varia para cada um deles.

Chamo a atenção para o método Read(). Enquanto estamos utilizando o DataReader é necessário termos uma conexão associada que por sua vez
acessa a Fonte de Dados. Esta conexão deve estar ativa, pois o DataReader percorre diretamente a Fonte de Dados resgatando os dados de acordo com
a Query. Esta conexão com a fonte de dados fica ativa até que você chame o método Close().

Já o método NextResult() utilizamos para verificar se existe um ou mais SQL Statements, pois se houver mais que um, utilizamos este método para
mover para o próximo Statement, e assim poderemos avançar e percorrer os registros que podem ou não serem retornados por este Statement.

Início da página

A Interface IDataRecord
Todas as classes que implementam a Interface IDataReader também implementam uma outra Interface chamada IDataRecord, o qual complementa o
DataReader. Esta interface fornece métodos (veja a Tabela 3) e propriedades (veja a Tabela 4) relativos aos registros (linhas e colunas) que são
retornados para o DataReader que é implementado justamente para ter acesso a base relacionais.

Tabela 3 - Propriedades da Interface IDataRecord

FieldCount Retorna o número de colunas do registro (linha) corrente

Item Retorna o valor de uma coluna especifica. Exitem um Overload, onde você pode passar um inteiro que corresponde ao índice da coluna ou
uma string correspondendo ao nome da coluna

Tabela 4 - Métodos da Interface IDataRecord

GetInt32 Resgata um valor do tipo Inteiro de uma determinada coluna

GetString Resgata um valor do tipo String de uma determinada coluna

http://www.microsoft.com/brasil/msdn/Tecnologias/vbnet/DataReader.mspx 10/05/2011
Explore o DataReader: por Israel Aéce Página 2 de 6

OBS.: Estou ocultando vários métodos por questões de espaço. Existem outros que não descreverei aqui, mas aos interessados que quiserem ver a lista
completa, podem consultar a documentação em: http://msdn.microsoft.com/library/default.asp?url=/library/en-
us/cpref/html/frlrfSystemDataIDataRecordMethodsTopic.asp.

Poderemos ver mais adiante que cada classe que implementa a interface IDataRecord cria métodos específicos para resgatar dados de um determinado
tipo relacionado aquele provider. No caso do SqlDataReader, além de termos os métodos da Interface IDataRecord implementados temos também
métodos como GetSqlInt32, GetSqlString. Utilizando os métodos específicos para resgatar um determinado tipo de dado próprio do provider
(GetSqlXXX), há também ganho de performance, sendo que menos passos são executados internamente, garantindo também o tipo de dado correto.

Vimos aqui como resgatamos um determinado valor de uma coluna, dado um índice que corresponde a coluna ou mesmo uma string que refere-se ao
nome da coluna. Vale lembrar que este índice e nome da coluna se referem a posição e nome que são definidos na sua Query. Exemplo:

SELECT Nome, Email, Estado FROM Usuarios ORDER BY Nome ASC

Neste caso, para ler as colunas retornadas use:

Coluna/Campo Por Nome Por Índice

Nome "Nome" 0

Email "Email" 1

Estado "Estado" 2

Início da página

Performance
Há dicas de performance que não poderíamos deixar de dizer, o qual falam justamente a melhor forma de acessar e resgatar os valores de uma
determinada coluna, já que a meta é criar uma aplicação rápida. Baseando no método GetString() que retorna um valor do tipo String de uma coluna,
veja como recuperar o conteúdo:

' VB.NET
Response.Write(dr("Nome"))
Response.Write(dr(0))
Response.Write(dr.GetString(0))

// C#
Response.Write(dr["Nome"]);
Response.Write(dr[0]);
Response.Write(dr.GetString(0));

Note que há três formas diferentes que retornar um valor de uma determinada coluna. Mas, o método mais rápido é o segundo, ou seja, passando
diretamente o valor inteiro que corresponde ao índice da coluna. E isso se firma ao fazer o Dissasembler da propriedade Item (que recebe o nome da
coluna como parâmetro) do DataReader, pois internamente ela verifica qual o índice (GetOrdinal(...)) para depois chamar o método que realmente
retorna o valor que chama-se GetValue(). Para entender melhor, veja:o seguinte código:

' VB.NET
'Através do Nome da Coluna
Public ReadOnly Property Item(ByVal name As String) As Object
Get
Return Me.GetValue(Me.GetOrdinal(name))
End Get
End Property

'Através do Índice da Coluna


Public ReadOnly Property Item(ByVal i As Integer) As Object
Get
Return Me.GetValue(i)
End Get
End Property

Agora fica claro porque passando o inteiro é mais rápido, pois não a há necessidade de chamar o método GetOrdinal(...) para saber qual o valor inteiro
que corresponde ao nome da Coluna. Algo que também pode não ser levado em consideração por muitos e o ganho também não é expressivo é a
utilização do "Proper Case", que também faz diferença. Veja o código:

' VB.NET
Response.Write(dr("Nome"))
Response.Write(dr("NOME"))

// C#
Response.Write(dr["Nome"]);
Response.Write(dr["NOME"]);

Escrevendo a coluna que deseja acessar com seu "case" correto, você também ganha em performance. Neste caso, "Nome" é a forma correta.

Início da página

As Interfaces IEnumerable e IEnumerator


Além das Interfaces IDataReader e IdataRecord, o DataReader ainda implementa mais uma interface chamada IEnumerable que através de um método
chamado GetEnumerator() retorna um enumerador (IEnumerator) para que seja possível iteragir a coleção.

Início da página

Utilizando o DataReader
Para que seja possível a criação de um DataReader (SqlDataReader) é necessário chamar o método ExecuteReader do objeto SqlCommand, o qual
retorna um objeto do tipo SqlDataReader e baseando na Query informada na propriedade CommandText do objeto SqlCommand retorna os registros
encontrados. Enquanto o SqlDataReader está em uso é necessário ter um objeto do tipo SqlConnection ativo, ou seja, uma conexão com a fonte de
dados. Veja neste código, como criar e resgatar os dados de uma tabela do banco de dados.

Atenção: onde você encontrar o "ConnectionString", substitua pela string de conexão do seu SQL Server com o banco de dados Northwind, por
exemplo: Database=Northwind; Server=(local); user id=sa; pwd=senha.

' VB.NET
Imports System.Data.SqlClient

Public Sub LerDados()


Dim query As String = "SELECT * FROM Categories"
Dim conexao As New SqlConnection("ConnectionString")

http://www.microsoft.com/brasil/msdn/Tecnologias/vbnet/DataReader.mspx 10/05/2011
Explore o DataReader: por Israel Aéce Página 3 de 6

Dim comando As New SqlCommand(query, conexao)


Dim dr As SqlDataReader
Try
conexao.Open()
dr = comando.ExecuteReader()
While dr.Read()
Console.WriteLine("Nome: " & _
dr.GetString(1) & ", ")
End While
Catch
Console.WriteLine("Ocorreu um Erro.")
Finally
dr.Close()
conexao.Close()
End Try
End Sub

// C#
using System.Data.SqlClient;

public void LerDados(){


string query = "SELECT * FROM Categories";
SqlConnection conexao = new SqlConnection("ConnectionString");
SqlCommand comando = new SqlCommand(query, conexao);
SqlDataReader dr = null;
try{
conexao.Open();
dr = comando.ExecuteReader();
while (dr.Read()){
Console.WriteLine("Nome: " +
dr.GetString(1) + ", ");
}
}
catch{
Console.WriteLine("Ocorreu um Erro.");
}
finally{
dr.Close();
conexao.Close();
}
}

Analisando o código perceba que importamos (Imports/using) o Namespace System.Data.SqlClient para que não seja preciso colocar o seu "caminho"
completo a todo momento em que utilizar uma classe. A função LerDados() cria uma conexão com a fonte de dados (SqlConnection) dado uma
Connection String. Criamos um SqlCommand e passamos em seu construtor o Query e a conexão a ser utilizada.

Abrimos a conexão com a fonte de dados e utilizamos o método ExecuteReader() do SqlCommand para executar a Query. O resultado é atribuído ao
objeto "dr" do tipo SqlDataReader (justamente o tipo de objeto que é devolvido com o método ExecuteReader()). Com um loop While ... End While
percorremos o SqlDataReader escrevendo o campo Nome, que é resgatado através do métdo GetString(). Repare que o loop While primeiramente
analisa se o método Read() retorna True, e assim sendo, executa o código. Lembrando que o método Read() avança para o próximo registro retornando
True se existir mais algum, caso contrário retorna False.

Existem situações de pesquisas que sabemos que só será retornado uma ou nenhuma linha. Neste caso a criação de um loop não é viável. Veja no
código seguinte a forma correta de tratar estas situações:

' VB.NET
Dim query As String = "SELECT * FROM Categories WHERE CategoryID = 1"

'cria SqlConnection e SqlCommand

conexao.Open()
dr = comando.ExecuteReader()
If dr.Read() Then
Console.WriteLine("Nome: " & _
dr.GetString(1))
Else
Console.WriteLine("Registro não encontrado.")
End If

// C#
string query = "SELECT * FROM Categories WHERE CategoryID = 1";

//cria SqlConnection e SqlCommand

conexao.Open();
dr = comando.ExecuteReader();
if(dr.Read())
Console.WriteLine("Nome: " +
dr.GetString(1) + ", ");
else
Console.WriteLine("Registro não encontrado.");

Início da página

Propriedade HasRows
Na versão 1.1 do .NET Framework foi criada nas classes XXXDataReader uma propriedade chamada HasRows, o qual retorna um valor Booleano
indicando se há ou não linhas no DataReader. Caso exista, True é retornado, caso contrário, False.

O uso desta propriedade é fundamental para verificar se foi ou não retornardo registro. Há casos em que é necessário usar e casos em que não é viavel.
O uso do método Read() além de retornar True caso encontre o próximo registro, ele move-se para o próximo registro. Sendo assim, quando for exibir
o conteúdo do DataReader, um registro a menos aparecerá. Talvez não tenho ficado muito claro, vamos ao exemplo com códigos. Imagine que a Query
retorne 5 registros e é preciso exibi-los ao cliente.

MSDN Magazine 1

MSDN Magazine 2

MSDN Magazine 3

MSDN Magazine 4

MSDN Magazine 5

' VB.NET

dr = comando.ExecuteReader()
If dr.Read() Then
While dr.Read()
Console.WriteLine("Nome: " & _
dr.GetString(1) & ", ")
End While
Else
Console.WriteLine("Registro não encontrado.")
End If

http://www.microsoft.com/brasil/msdn/Tecnologias/vbnet/DataReader.mspx 10/05/2011
Explore o DataReader: por Israel Aéce Página 4 de 6

// C#

dr = comando.ExecuteReader();
if(dr.Read()){
while (dr.Read()){
Console.WriteLine("Nome: " +
dr.GetString(1) + ", ");
}
}
else
Console.WriteLine("Registro não encontrado.");

Output (VB.NET e C#):


MSDN Magazine 2
MSDN Magazine 3
MSDN Magazine 4
MSDN Magazine 5

Neste caso a exibição não será como esperamos, pois somente 4 registros são retornados. Isso acontece devido a utilização do método Read() para
verificar a existência de registros. Como o Read() avança o DataReader e o mesmo é "foward-only", já foi passado pelo primeiro registro, não sendo
possível retornar até a primeira posição.

A solução para este problema é a utilização da propriedade HasRows ao invés do método Read() ao fazer a condicional, ficando da seguinte forma:

' VB.NET

If dr.HasRows Then
While dr.Read()
Console.WriteLine("Nome: " & _
dr.GetString(1) & ", ")
End While
Else
Console.WriteLine("Registro não encontrado.")
End If

// C#

if(dr.HasRows){
while (dr.Read()){
Console.WriteLine("Nome: " +
dr.GetString(1) + ", ");
}
}
else
Console.WriteLine("Registro não encontrado.");

Só que há um caso em que o método Read() é viável, quando você tem certeza que será retornado no máximo 1 (um) registro/linha, pois é possível
verificar se é retornado True (encontrado o registro) e ler o mesmo.

' VB.NET

If dr.Read() Then
Console.WriteLine("Nome: " & _
dr.GetString(1) & ", ")
Else
Console.WriteLine("Registro não encontrado.")
End If

// C#

if(dr.Read())
Console.WriteLine("Nome: " +
dr.GetString(1) + ", ");
else
Console.WriteLine("Registro não encontrado.");

Como propriedade HasRows não existe na versão 1.0 do .NET Framework, uma alternativa é verificar a existência ou não de linhas na leitura através de
uma condição no método Read(). Caso seja atendida, crie um loop Do...While percorrendo o DataReader até que o método Read() retorne False, ou
seja, quando não houver mais registros. Assim, o primeiro registro sempre será exibido, pois a condição está no final do loop.

' VB.NET

If dr.Read() Then
Do
Console.WriteLine("Nome: " + _
dr.GetString(1))
Loop Until Not dr.Read()
Else
Console.WriteLine("Registro não encontrado.")
End If

// C#

if(dr.Read())
do
{
Console.WriteLine("Nome: " +
dr.GetString(1));
}while(dr.Read());
else
Console.WriteLine("Registro não encontrado.");

Início da página

Método NextResult
Geralmente dentro de Stored Procedures é comum o uso mais de um SQL Statement.

SELECT Nome FROM Usuarios ORDER BY Nome ASC


SELECT Nome FROM Categorias ORDER BY Nome ASC

Neste caso use o método NextResult() que avança (caso exista) para o próximo SQL Statement.

' VB.NET

Do
While dr.Read()
Console.WriteLine("Nome: " +
dr.GetString(0))
End While
While dr.NextResult()

http://www.microsoft.com/brasil/msdn/Tecnologias/vbnet/DataReader.mspx 10/05/2011
Explore o DataReader: por Israel Aéce Página 5 de 6

// C#

do
{
while(dr.Read())
{
Console.WriteLine("Nome: " +
dr.GetString(1));
}
}while(dr.NextResult());

Aqui deve-se atentar para resgatar o valor de uma ou mais colunas do DataReader, pois se utilizar o nome da mesma, tem que ter a certeza absoluta
que este nome existirá nos dois (ou nos N) conjutos de dados. Utilizar o índice seria uma forma de "burlar" isso, mas neste caso, tem que informar um
índice que também tenha nos dois (ou N) conjutos de dados.

Início da página

Enumerador CommandBehavior.CloseConnection
Através deste Enumerador podemos definir alguns passos a serem executados pelo DataReader. O principal item deste Enumerador é o
CloseConnection, o qual tem um comportamento bastante interessante. Ao definí-lo e passarmos como parâmetro para o método ExecuteReader() do
objeto Command, não há a necessidade de fechar explicitamente a conexão com a fonte de dados, ou seja, aos percorrer os registros o DataReader se
encarrega de fazer isso automaticamente quando chamar o método Close() do DataReader.

' VB.NET

Imports System.Data.SqlClient

Public Sub LerDados()


Dim conexao As New SqlConnection("ConnectionString")
Dim comando As New SqlCommand("QUERY", conexao)
Dim dr As SqlDataReader
Try
conexao.Open()
dr = comando.ExecuteReader(CommandBehavior.CloseConnection)
While dr.Read()
'…
End While
Catch ex As Exception
Console.WriteLine("Ocorreu um Erro.")
Finally
dr.Close()
End Try
End Sub

// C#

using System.Data.SqlClient;

public void LerDados(){


SqlConnection conexao = new SqlConnection("ConnectionString");
SqlCommand comando = new SqlCommand("QUERY", conexao);
SqlDataReader dr;
try{
conexao.Open();
dr = comando.ExecuteReader(CommandBehavior.CloseConnection);
while (dr.Read()){
//…
}
}
catch (Exception ex){
Console.WriteLine("Ocorreu um Erro.");
}
finally{
dr.Close();
}
}

Note que a conexão não é fechada explicitamente com o Banco de Dados (conexao.Close()). Se quiser se certificar disto, teste se a conexão ainda
continua ou não aberta após o fechamento do DataReader, verificando a Status da mesma.

' VB.NET

'…
dr.Close()
If Not (conexao.State = ConnectionState.Closed) Then
conexao.Close()
End If

// C#

//…
dr.Close();
if (conexao.State != ConnectionState.Closed)
conexao.Close();

Se definir o CommandBehavior como CloseConnection, o método Close() da conexão não será chamado, ou seja, a condição falha, pois a conexão com a
Base de Dados já foi fechada pelo DataReader.

Início da página

Retornando DataReaders através de Funções


Há casos onde é retornado ao Cliente pela camada de acesso à dados objetos do tipo DataReader. Seja ele qual for (OleDb, Sql, Odbc ou Oracle) é um
tanto perigoso. Como já sabemos, para que o DataReader resgate os valores da Fonte de Dados, é necessário ter uma conexão ativa com o mesmo,
caso contrário, não conseguirá.

Neste cenário, o que realmente é comum são funções que retornam DataReaders, utilizando a opção CloseConnection do Enumerador
CommandBehavior, que faz sentido, deixando a cargo do DataReader fechar a conexão com a Fonte de Dados assim que finalizar seu trabalho. Com
isso, temos dois problemas:

• Ao retornar XXXDataReader para a camada de negócios, ou mesmo para a camada de apresentação, gera-se uma dependência do Provider, ou seja,
se futuramente pretender migrar de Base Dados/Provider terá que rescrever tudo;

• Quando não se utiliza alguma DAL (Data Access Layer) para executar comandos e gerir as conexões com a Fonte de Dados, fica a cargo* do Cliente
fechar o DataReader, pois sabemos que temos uma conexão o servindo qual será/deverá ser fechada quando o DataReader for fechado e, em muitos
casos por algum descuido, pode não ser fechado o DataReader, e consequentemente "estourando" o pool de conexões com a Fonte de Dados,
impedindo assim que se abram novas conexões.

Uma observação importante para quem utiliza a DAL MS DAAB (Microsoft Data Access Application Block) é utilizar a versão 3.0 da mesma, pois o seu
design mudou em relação a versão 2.0 (que só atendia à SQL Server), adotando o padrão Abstract Factory, tornando assim o código genérico e agora
suportando OleDb e OracleClient.

http://www.microsoft.com/brasil/msdn/Tecnologias/vbnet/DataReader.mspx 10/05/2011
Explore o DataReader: por Israel Aéce Página 6 de 6

* DALs também permitem que se retarde o fechamento da conexão com a Base de Dados, o qual deve se feita explicitamente.

Início da página

Populando controles ASP.NET


Na maioria das vezes utiliza-se o DataReader para resgatar dados de um Banco de Dados e através dele preencher controles para exibição dos dados. É
possível atribuí-lo a propriedade DataSource desses controles e exibir os dados de acordo com a necessidade. É importante dizer que continua sendo
necessário a especificação os campos a serem exibidos (salvo se utilizar o controle DataGrid e definir a opção Auto Generate Columns como True). Para
atribuir o DataReader à propriedade DataSource dos controles, use:

' VB.NET

dr = comando.ExecuteReader(CommandBehavior.CloseConnection)
Me.[TeuControle].DataSource = dr
Me.[TeuControle].DataBind()

// C#

dr = comando.ExecuteReader(CommandBehavior.CloseConnection);
this.[TeuControle].DataSource = dr;
this.[TeuControle].DataBind();

Início da página

Conclusão
Como vimos, existe um grande objeto que disponibiliza recursos para leitura de dados de forma conectada e rápida. Com ele garantimos uma
perfomance muito maior no acesso e resgate aos dados de uma base de dados qualquer com relação ao objeto DataSet. Mas é preciso atentar a
diversas regras e boas práticas para o uso do mesmo, para que algo tão poderoso e eficaz não torne um problema, dificultando a manutenção e
principalmente a eficácia e performance que ele nos fornece.

Israel Aéce (israel@projetando.net) é MVP, líder de Grupo de Usuários Projetando.NET (http://www.projetando.net) e (EE) INETA Voluntário Latam.

OLHOS:
A Plataforma .NET fornece um objeto chamado DataReader, o qual está contido dentro do Namespace System.Data e que por sua vez tem como
finalidade resgatar os dados da base de dados de forma extremamente rápida.

O uso do HasRows é fundamental para verificar se foi ou não retornardo registro

Início da página
Versão para Impressão Enviar esta Página Adicionar a Favoritos

Como você classificaria a utilidade desta página?

1 2 3 4 5
Ruim Ótima

Indique por que você classificou esta página dessa forma. (opcional)

Enviar

Fale Conosco | Imprima esta página | Adicione aos Favoritos

©2011 Microsoft Corporation. Todos os direitos reservados. Entre em contato | Nota Legal | Marcas comerciais | Política de Privacidade

http://www.microsoft.com/brasil/msdn/Tecnologias/vbnet/DataReader.mspx 10/05/2011