Dicas de performance com o ADO.NET

Trabalhar com banco de dados sempre foi uma funcionalidade interessante no tocante à forma de ler e pesquisar dados. Quando o ADO.NET surgiu, vi muitos desenvolvedores completamente perdidos sem saber qual é o tipo mais adequado de classe e qual método usar em determinada situação. O objetivo deste artigo é esclarecer as melhores funcionalidades do ADO.NET neste ponto. Mostrarei diversas formas de se pesquisar dados nas tabelas no Northwind do SQL Server, mas cabe ressaltar que a fonte de dados, ou seja, onde estão os dados é irrelevante, pois a classe é a mesma para todos, seja SQL Server, Oracle, Access, etc.

Execute Reader

O desenvolvedor sempre deve ter em mente: “Qual é a melhor forma de ler várias colunas de dados?”. Note que a pergunta está muito clara, ou seja, se refere apenas à leitura de várias colunas de dados. A resposta é: DataReader. O DataReader é a forma mais rápida de se ler dados em uma fonte de dados. Ela foi criada especialmente para esta finalidade. O que você deverá fazer é ler os dados e preencher o devido controle para exibi-los.

Para quem já trabalhou com Recordset, o DataReader é o que mais se aproxima disso, pois é somente forward-only e você não consegue lê-lo mais que uma vez, ou seja, uma vez lido já era. A performance é a melhor possível, pois você precisa apenas dos seguintes dados: uma conexão, um command (instrução SQL), método ExecuteReader.

Vamos a um exemplo. Abra o Visual Studio .NET 2003 e crie um novo projeto chamado ADO.NET do tipo Windows Forms, usando a linguagem Visual Basic .NET (poderia ser em C#, é apenas uma questão de sintaxe, aliás se você programa em C#, as classes e os métodos usados são idênticos, portanto, esteja à vontade quanto à linguagem).

Crie um novo formulário chamado DataReader com o seguinte layout (Figura 1), contendo: 3 Buttons e 1 ListBox (lstDados).

Layout do formulário DataReader.
Figura 1. Layout do formulário DataReader.

Pressione F7 para exibir a janela de códigos e como iremos acessar o SQL Server, declare o devido namespace na lista de Imports na primeira linha da janela. Esta classe foi criada especialmente para o SQL Server versão 7.0 ou superior.


        Imports System.Data.SqlClient
        

Em seguida, dentro da declaração da classe, declare uma variável chamada conexao para a conexão com o banco de dados. É importante ressaltar que você deverá ajustar esta string de conexão de acordo com a sua configuração, adicionando, se houver, password, segurança integrada, etc. Como aqui é apenas um artigo, deixei da forma mais simples possível.


        Public Class DataReader

        Inherits System.Windows.Forms.Form

        Dim conexao As String = "Server=(local);Database=Northwind;user id=sa"
        

Em seguida, crie o código para o botão LerNomeCampo (btnLerNomeCampo) (ver Listagem 1). Note que existe um bloco de Try/Catch para tratar possíveis erros. Se ocorrer algum erro dentro do bloco do Try, então o mesmo é capturado dentro do Catch e é exibida a mensagem de erro.

Listagem 1. Código para o botão LerNomeCampo.

        Private Sub btnLerNomeCampo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
        btnLerNomeCampo.Click

        Try

        Dim conn As New SqlConnection(conexao)

        Dim sql As String = "Select CustomerID, CompanyName FROM Customers"

        Dim cmd As New SqlCommand(sql, conn)

        conn.Open()

        Dim reader As SqlDataReader = cmd.ExecuteReader()

        lstDados.Items.Clear()

        While reader.Read()

        lstDados.Items.Add(reader("CustomerID") + " - " + _reader("CompanyName"))

        End While

        reader.Close()

        conn.Close()

        Catch ex As SqlException

        MessageBox.Show(ex.Message)

        End Try

        End Sub
        

Veja a explicação detalhada. É definida a variável conn para instanciar a classe SqlConnection que recebe a string de conexão já definida acima.


        Dim conn As New SqlConnection(conexao)
        

É definida a instrução SQL e um Command, onde a variável cmd é o Command que irá executar exatamente o Sql da respectiva conexão. Em seguida, é aberta a conexão com o método Open.


        Dim sql As String = "Select CustomerID, CompanyName FROM Customers"

        Dim cmd As New SqlCommand(sql, conn)

        conn.Open()
        

Para montar um DataReader, basta instanciar uma variável com o objeto SqlDataReader e usar o método ExecuteReader. É exatamente neste momento que todos os dados contidos na instrução SQL vão para a memória e ficam armazenados no objeto reader. Isto ocorre apenas na memória.


        Dim reader As SqlDataReader = cmd.ExecuteReader()
        

Como iremos exibir os dados no ListBox lstDados, então, o mesmo é limpo e em seguida é montado um looping do tipo While que percorre todos os dados do DataReader Reader. Observe que não existe um método MoveNext, conforme tínhamos no Recordset, isto está encapsulado no método Read(). Observe como referenciar os campos do DataReader, ou seja, a primeira forma é através do próprio nome do campo (entre aspas “campo”). Apesar de não ser a forma mais rápida, é a mais indicada em muitas situações. A cada item lido, o mesmo é adicionado como um novo item no lstDados.


        lstDados.Items.Clear()

        While reader.Read()

        lstDados.Items.Add(reader("CustomerID") + " - " + _reader("CompanyName"))

        End While
        

Por fim, são fechados o Reader e a conexão.


        reader.Close()

        conn.Close()
        

Outra forma de ler os dados no DataReader é através do número ordinal. Digite o código da Listagem 2 para o botão btnLerOrdinal.

Listagem 2. Código para o botão LerOrdinal.

        Private Sub btnLerOrdinal_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
        btnLerOrdinal.Click

        Dim conn As New SqlConnection(conexao)

        Dim sql As String = "Select CustomerID, CompanyName FROM Customers ORDER BY CompanyName"

        Dim cmd As New SqlCommand(sql, conn)

        conn.Open()

        Dim reader As SqlDataReader = cmd.ExecuteReader()

        Dim intCustomerIdOrdinal As Integer = reader.GetOrdinal("CustomerID")

        Dim intCompanyNameOrdinal As Integer = reader.GetOrdinal("CompanyName")

        lstDados.Items.Clear()

        While reader.Read()

        lstDados.Items.Add(reader(intCustomerIdOrdinal) + " - " + reader(intCompanyNameOrdinal))

        End While

        reader.Close()

        conn.Close()

        End Sub
        

Basicamente é igual ao anterior, exceto na forma de trabalhar com os dados. Note que cada campo que será usado é declarado com o respectivo tipo de variável, neste caso integer, e o mesmo é referenciado com o reader.GetOrdinal seguido do nome do campo. No looping você referencia a variável o qual foi armazenado o respectivo campo. A vantagem disto é que dependendo do número de campos e dados a serem lidos, pode tornar-se mais rápido justamente pela não necessidade de ficar convertendo campos, pois o tipo já está declarado na variável. Veja ainda outra forma de você referenciar os campos na Listagem 3. Este é o código do botão btnLerObjeto.

Listagem 3. Código para o botão LerObjeto.

        Private Sub btnLerObjeto_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
        btnLerObjeto.Click

        Dim conn As New SqlConnection(conexao)

        Dim sql As String = "Select CustomerID, CompanyName FROM Customers ORDER BY CustomerID"

        Dim cmd As New SqlCommand(sql, conn)

        conn.Open()

        Dim reader As SqlDataReader = cmd.ExecuteReader()

        Dim intCustomerIdOrdinal As Integer = reader.GetOrdinal("CustomerID")

        Dim intCompanyNameOrdinal As Integer = reader.GetOrdinal("CompanyName")

        lstDados.Items.Clear()

        While reader.Read()

        lstDados.Items.Add(reader.GetString(intCompanyNameOrdinal) + _

        " - " + reader.GetString(intCustomerIdOrdinal))

        End While

        reader.Close()

        conn.Close()

        End Sub
        

Basicamente idêntico ao anterior, exceto na referência dos campos. É usado o GetOrdinal, mas no looping o objeto é capturado usando o GetString. Cabe ressaltar que você também pode se referenciar pelo índice (0, 1, 2, etc) e, apesar de eu não gostar disto por uma questão de acessibilidade e documentação, é a forma mais rápida de referenciar uma coluna do DataReader.

Salve o projeto, defina-o como sendo o Startup Project e pressione F5 para executá-lo (Figura 2). Faça o teste com os três botões.

 Execução do DataReader
Figura 2. Execução do DataReader.

ExecuteScalar

O método ExecuteScalar é outra maneira de ler dados em uma fonte, no entanto, tenha em mente a seguinte questão: “Preciso ler apenas uma linha e uma coluna de uma fonte de dados”. O ExecuteScalar foi criado para este tipo de situação, onde você precisa retornar apenas uma linha e uma coluna. Atenção, eu não escrevi duas linhas ou duas colunas, apenas uma linha e coluna. São casos em que você precisa saber o valor de um produto, a quantidade de estoque, o saldo bancário, o status de um cliente, enfim, são situações em que o retorno é usado para tal finalidade. Vale dizer que o ExecuteScalar retorna um tipo Object e você precisa convertê-lo para o tipo adequado no momento do uso.

O ExecuteScalar é o meio mais rápido existente para qualquer tipo de pesquisa. Não existe nenhum método mais rápido que ele nestas situações em select em tabelas. Além disto, cabe ressaltar que instruções do tipo Agreggate (Sum, Count, Max, Min, Avg, etc) também são aceitas. Assim como o ExecuteReader, o ExecuteScalar precisa apenas de uma conexão, um Command e o método ExecuteScalar.

Crie um novo formulário chamado ExecuteScalar contendo os seguintes controles, conforme a Figura 3: um ListBox (lstProdutos) e dois Buttons.

Layout do formulário ExecuteScalar.
Figura 3. Layout do formulário ExecuteScalar.

Pressione F7 para exibir a janela de códigos e como iremos ler um banco de dados, na primeira linha da janela, digite a referência ao namespace para acessar o SQL Server.


        Imports System.Data.SqlClient
        

Declare a string de conexão com o banco de dados Northwind do SQL Server.


        Dim conexao As String = "Server=(local);Database=Northwind;user id=sa"
        

Como o ListBox deverá ser preenchido assim que o formulário for inicializado, então declare a rotina (MontaProdutos) que irá montar o ListBox com todos os produtos da tabela Products (ver Listagem 4).

Listagem 4. Declarando a rotina MontaProdutos.

        Public Sub New()

        MyBase.New()

        'This call is required by the Windows Form Designer.

        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

        MontaProdutos()

        End Sub
        

Digite a respectiva rotina que irá montar um DataReader com os nomes dos produtos da tabela Products e no looping, irá preencher o lstProdutos com cada nome (ver Listagem 5).

Listagem 5. Rotina MontaProdutos.

        #Region " Monta a lista de produtos "

        Private Sub MontaProdutos()

        Try

        Dim conn As New SqlConnection(conexao)

        Dim sql As String = "Select ProductName FROM Products"

        Dim cmd As New SqlCommand(sql, conn)

        conn.Open()

        Dim reader As SqlDataReader = cmd.ExecuteReader()

        lstProdutos.Items.Clear()

        While reader.Read()

        lstProdutos.Items.Add(reader("ProductName"))

        End While

        reader.Close()

        conn.Close()

        Catch ex As SqlException

        MessageBox.Show(ex.Message)

        End Try

        End Sub

        #End Region
        

Digite o código para o botão btnQtde, o qual irá pesquisar a quantidade de produtos existentes na tabela Products. Note que a instrução SQL é uma função de agregação Count(*) (ver Listagem 6). Observe ainda que é chamada a função GetExecuteScalar a ser criada por nós porque iremos implementar esta mesma funcionalidade nos demais controles. Esta rotina precisa de dois argumentos, a instrução SQL e o título da janela a ser exibido.

Listagem 6. Código para o clique do botão btnQtde.

        Private Sub btnQtde_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnQtde.Click

        Try

        GetExecuteScalar("Select Count(*) FROM Products", "Produtos cadastrados: ")

        Catch ex As Exception

        MessageBox.Show(ex.Message)

        End Try

        End Sub
        

Digite a função GetExecuteScalar, a qual recebe dois argumentos do tipo String: sql, que é a respectiva instrução SQL; title, que é o título do MessageBox. Note que existe um tratamento de erro com o Try/Catch e, caso dê algum erro, o Throw no Catch é que avisará a rotina que a chamou que houve um erro. Neste caso, você precisará tratar o erro no Catch da respectiva rotina.

Nesta rotina temos a abertura da conexao com o banco de dados usando a variável conexao aberta anteriormente, um SqlCommand para executar a instrução SQL da respectiva conexão definida, e por fim, a abertura da conexão.

Em seguida é exibido um MessageBox.Show para exibir o resultado. No entanto, este resultado é justamente a execução do método ExecuteScalar do objeto cmd (SqlCommand). Note que ele é convertido para ToString (no C# use o Convert.ToString()) porque o ExecuteScalar retorna um tipo Object. Por fim, é fechada a conexão (ver Listagem 7).

Listagem 7. Rotina GetExecuteScalar.

        Private Sub GetExecuteScalar(ByVal sql As String, ByVal title As String)

        Try

        Dim conn As New SqlConnection(conexao)

        Dim cmd As New SqlCommand(sql, conn)

        conn.Open()

        MessageBox.Show(title + cmd.ExecuteScalar().ToString(), "SQL Magazine")

        conn.Close()

        Catch ex As SqlException

        Throw

        End Try

        End Sub
        

Com a rotina GetExecuteScalar criada, digite o código para ser disparado quando você clicar no botão btnEstoque ou selecionar um produto no ListBox (ver Listagem 8). Note que no VB.NET isto é definido no Handles da linha Private Sub, facilitando a maneira de chamar rotinas, ou seja, basta declarar o objeto seguido do evento. Neste caso, temos o objeto lstProdutos seguido do evento SelectedIndexChanged e o objeto btnEstoque seguido do evento Click.

Adicione um IF para saber se algum produto está selecionado, caso contrário, exiba uma mensagem ao usuário e abandone a rotina. Caso tenha um produto selecionado, implemente a rotina de tratamento de erro Try/Catch e chame a rotina GetExecuteScalar passando como argumento a instrução SQL, o qual seleciona o campo UnitsInStock da tabela Products com a condição Where ProductName igual ao produto selecionado.

Observe o uso do Replace na condição, pois se algum produto contiver o apóstrofo, o mesmo será trocado por dois apóstrofos para evitar erros de pesquisa.

Listagem 8. Rotina para chamar o GetExecuteScalar.

        Private Sub btnEstoque_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
        lstProdutos.SelectedIndexChanged, btnEstoque.Click

        If lstProdutos.SelectedIndex = -1 Then

        MessageBox.Show("Por favor, selecione um produto")

        Exit Sub

        End If

        Try

        GetExecuteScalar("Select UnitsInStock FROM Products Where ProductName='" + _

        lstProdutos.SelectedItem.ToString.Replace("'", "''") + "'", _

        "Qtde em estoque: ")

        Catch ex As Exception

        MessageBox.Show("O produto contém caracteres inválidos")

        End Try

        End Sub
        

Salve o projeto e execute-o para ver o resultado (Figura 4). Faça os testes com os botões e caso queira, aplique um contador para saber o tempo de resposta fantástico que terá.

Execução do ExecuteScalar.
Figura 4. Execução do ExecuteScalar.

DataSet

DataSet é um conceito fácil de entender. Imagine que você tenha um repositório na memória RAM contendo quantas tabelas forem necessárias, e cada uma destas tabelas podem ter origens de dados diferentes, seja um banco de dados SQL Server, outra do Oracle, outra de um arquivo XML, outra da pasta Contatos do Outlook, enfim, a origem dos dados não importa neste caso, o importante é saber que o DataSet é um espaço na memória que pode conter diversas tabelas (chamadas DataTables). Qual é o limite de DataTables no DataSet? É o limite da memória!

Um DataSet é utilizado em situações onde você precisa manter os dados na memória, seja por motivos de atualizações dos mesmos, relacionamentos, paginações (no caso do ASP.NET), enfim, os dados estarão sempre na memória e nas respectivas DataTables.

Uma DataTable contém linhas (Rows) e colunas (Columns) e não necessariamente devem ter uma fonte de dados, você pode criar e manipular uma DataTable somente na memória. Cada linha de uma DataTable contém o status da mesma, informando se foi adicionada, excluída, alterada, etc. Com isso, você poderá usar o método Update do DataAdapter para sincronizar os dados com a tabela na fonte de dados.

Tudo o que você precisa para criar um DataSet é uma conexão, um DataAdapter, um DataSet, uma instrução SQL (no caso de ser uma tabela oriunda de um banco de dados) para preencher a DataTable com o comando Fill no respectivo DataSet.

Como disse no início, o foco deste artigo é abordar as diversas maneiras de consultar dados, mas o objeto DataAdapter permite instruções Insert, Delete, Select e Update. Pesquise sobre este objeto para saber maiores informações.

Crie um novo formulário chamado DataSetGeral contendo os seguintes controles (Figura 5): dois Buttons, dois DataGrids (gridCategorias e gridProdutos), um ListBox (lstCategorias) e um TextBox (txtClientes, TextMode=Multiline).

Layout do formulário de DataSet.
Figura 5. Layout do formulário de DataSet.

Pressione F7 para exibir a janela de códigos e adicione a referência ao namespace do SQL Server na primeira linha.


        Imports System.Data.SqlClient
        

Digite a string de conexão que usaremos nos dois botões.


        Dim conexao As String = "Server=(local);DataBase=Northwind;User ID=sa;Password="
        

Digite o código para o botão btnDataTable (ver Listagem 9), o qual irá preencher todos os controles com os respectivos dados oriundos de cada uma das tabelas declaradas contidas no Northwind.

Listagem 9. Código para o botão btnDataTable.

        Private Sub btnDataTable_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
        btnDataTable.Click

        Dim conn As New SqlConnection(conexao)

        'instruções SQL

        Dim sqlCategories As String = "Select CategoryID, CategoryName From Categories"

        Dim sqlCustomers As String = "Select CompanyName, ContactName From Customers"

        Dim sqlProducts As String = "Select * From Products"

        'DataAdapters

        Dim adapterCategories As New SqlDataAdapter(sqlCategories, conn)

        Dim adapterCustomers As New SqlDataAdapter(sqlCustomers, conn)

        Dim adapterProducts As New SqlDataAdapter(sqlProducts, conn)

        'define o DataSet

        Dim ds As New DataSet

        conn.Open()

        'Fill no DataSet

        adapterCategories.Fill(ds, "categorias")

        adapterCustomers.Fill(ds, "clientes")

        adapterProducts.Fill(ds, "produtos")

        conn.Close()



        gridCategorias.DataSource = ds.Tables("categorias").DefaultView

        With lstCategorias

        .DataSource = ds.Tables("categorias").DefaultView

        .DisplayMember = "CategoryName"

        .ValueMember = "CategoryID"

        End With



        txtClientes.Text = String.Empty

        Dim cliente As DataRow

        For Each cliente In ds.Tables("clientes").Rows

        txtClientes.Text += cliente("CompanyName").ToString() + _

        " - " + cliente("ContactName") + vbNewLine

        Next



        gridProdutos.DataSource = ds.Tables("produtos").DefaultView

        End Sub
        

Veja a explicação detalhada do código. É declarada a string de conexão para abrir o banco de dados e existem três variáveis do tipo string contendo as três instruções SQL que selecionam os dados de diferentes tabelas. Note que associei o nome de cada variável conforme a tabela no Select.


        Dim conn As New SqlConnection(conexao)

        'instruções SQL

        Dim sqlCategories As String = "Select CategoryID, CategoryName From Categories"

        Dim sqlCustomers As String = "Select CompanyName, ContactName From Customers"

        Dim sqlProducts As String = "Select * From Products"
        

São declarados os três DataAdapters das respectivas instruções SQL e conexão. Cada DataAdapter precisa ser declarado para que o DataSet saiba qual DataTable conterá os respectivos dados usados no método Fill, a seguir.


        Dim adapterCategories As New SqlDataAdapter(sqlCategories, conn)

        Dim adapterCustomers As New SqlDataAdapter(sqlCustomers, conn)

        Dim adapterProducts As New SqlDataAdapter(sqlProducts, conn)
        

É declarado o DataSet e aberta a conexão. Note que temos apenas um DataSet e três DataAdapters.


        Dim ds As New DataSet

        conn.Open()
        

Aqui usamos o método Fill do respectivo DataAdapter para preencher a DataTable na memória. É exatamente nesta hora que a tabela é preenchida no DataSet. A declaração dos nomes das tabelas não é obrigatório, mas desejável para facilitar o entendimento. Caso queira omitir os nomes das tabelas, quando precisar usá-las terá que se referir pelo índice. Apesar do uso do índice ser mais rápido, nem sempre é a melhor opção quando temos diversas tabelas, simplesmente por uma questão de entendimento. Em seguida, como os dados já estão na memória, você deve fechar a conexão com o método Close.


        adapterCategories.Fill(ds, "categorias")

        adapterCustomers.Fill(ds, "clientes")

        adapterProducts.Fill(ds, "produtos")

        conn.Close()
        

Veja como associar a origem do gridCategorias a respectiva tabela contida no DataSet chamada “categorias”. Basta você usar o nome do DataSet (ds) seguido do Tables e do nome da respectiva tabela. Caso opte pelo uso do índice, a sintaxe seria ds.Tables(0). O uso do DefaultView não é obrigatório, pois em situações onde você alterou a estrutura da DataTable na memória, o uso do DefaultView recupera a estrutura original somente para efeito de exibição de dados.


        gridCategorias.DataSource = ds.Tables("categorias").DefaultView
        

No caso do ListBox lstCategorias, temos a mesma forma de definir a origem dos dados, ou seja, no DataSource é declarado DataSet (ds) seguido da DataTable “categorias”. Neste controle é preciso informar qual o campo será exibido (displayMember = CategoryName) e o campo a ser armazenado (ValueMember = CategoryID).


        With lstCategorias

        .DataSource = ds.Tables("categorias").DefaultView

        .DisplayMember = "CategoryName"

        .ValueMember = "CategoryID"

        End With
        

Já no TextBox MultiLine, o mesmo é limpo e, em seguida é feito um looping para percorrer todas as linhas contidas na tabela clientes. Para cada linha lida é exibido o conteúdo no txtClientes. Note a forma de referenciar um campo, ou seja, cliente (que é o objeto DataRow) seguido do nome da coluna. Em seguida é usada a constante vbNewLine para provocar uma quebra de linha.


        txtClientes.Text = String.Empty

        Dim cliente As DataRow

        For Each cliente In ds.Tables("clientes").Rows

        txtClientes.Text += cliente("CompanyName").ToString() + _

        " - " + cliente("ContactName") + vbNewLine

        Next
        

Por fim, resta montar a origem do gridProdutos, que é a tabela produtos contida no DataSet.


        gridProdutos.DataSource = ds.Tables("produtos").DefaultView
        

Já o código do botão btnUmDataSet tem uma novidade, mesmo para quem já trabalhou com ADO.NET ou já montou diversos códigos conforme o outro botão (ver Listagem 10). Este código usa o recurso que o DataAdapter contém para criar um único DataSet com diversas instruções Select no mesmo DataAdapter. Neste caso, a memória terá um conjunto de DataTables associadas por um índice, e indico mapear a memória com as DataTables para facilitar o uso.

Listagem 10. Código para o botão btnUmDataSet.

        Private Sub btnUmDataSet_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
        btnUmDataSet.Click

        Dim conn As New SqlConnection(conexao)

        Dim sql As New System.Text.StringBuilder

        sql.Append("Select CategoryID, CategoryName From Categories;")

        sql.Append("Select CompanyName, ContactName From Customers;")

        sql.Append("Select * From Products;")

        Dim adapter As New SqlDataAdapter(sql.ToString(), conn)

        Dim ds As New DataSet

        conn.Open()



        adapter.TableMappings.Add("Dados", "Categorias")

        adapter.TableMappings.Add("Dados1", "Clientes")

        adapter.TableMappings.Add("Dados2", "Produtos")

        adapter.Fill(ds, "Dados")

        conn.Close()



        gridCategorias.DataSource = ds.Tables("Categorias").DefaultView

        With lstCategorias

        .DataSource = ds.Tables("Categorias").DefaultView

        .DisplayMember = "CategoryName"

        .ValueMember = "CategoryID"

        End With



        txtClientes.Text = String.Empty

        Dim cliente As DataRow

        For Each cliente In ds.Tables("Clientes").Rows

        txtClientes.Text += cliente("CompanyName").ToString() + _

        " - " + cliente("ContactName") + vbNewLine

        Next



        gridProdutos.DataSource = ds.Tables("Produtos").DefaultView

        End Sub
        

Veja a explicação detalhada. A conexão é definida e instanciada na variável conn. Como existem diversas instruções SQL a serem executadas, e todas farão parte da mesma instrução Fill do DataAdapter, então, é indicado usar a classe System.Text.StringBuilder. Toda e qualquer manipulação de strings com o StringBuilder é mais rápida que qualquer outro meio. Neste caso, observe todas as instruções SQL Select.


        Dim conn As New SqlConnection(conexao)

        Dim sql As New System.Text.StringBuilder

        sql.Append("Select CategoryID, CategoryName From Categories;")

        sql.Append("Select CompanyName, ContactName From Customers;")

        sql.Append("Select * From Products;")
        

Em seguida é definido apenas um DataAdapter, o qual será executado com as respectivas instruções SQL na conexão atual. É definido o DataSet que conterá todas as DataTables e, por fim, é aberta a conexão.


        Dim adapter As New SqlDataAdapter(sql.ToString(), conn)

        Dim ds As New DataSet

        conn.Open()
        

Agora é a hora de mapear a memória com as DataTables. Para isso, use o adapter.TableMappings.Add, o qual possui dois argumentos: o nome da Table atual na memória e o novo nome. É importante saber como as Tables são nomeadas na memória e a regra é simples. O método Fill informa ao DataSet o nome da DataTable, neste caso, Dados. Sendo assim, na memória são criadas as Tables chamadas Dados1, Dados2 e assim sucessivamente, ou seja DadosN. Este número (índice) é de acordo com o número de Selects executados nas instruções SQL. Apesar de parecer estranho mapear antes de preencher, o DataAdapter requer isto. Sendo assim, você já sabe como as DataTables são nomeadas e a regra para renomea-las.


        adapter.TableMappings.Add("Dados", "Categorias")

        adapter.TableMappings.Add("Dados1", "Clientes")

        adapter.TableMappings.Add("Dados2", "Produtos")

        adapter.Fill(ds, "Dados")
        

O restante do código é idêntico ao anterior, onde usei as tabelas nomeadas para preencher os controles. Caso não tivessem renomeadas, bastaria usar o nome original contido no DataSet, ou seja, Dados, Dados1 e Dados2. Salve o projeto e execute-o para ver o resultado (Figura 6).

Execução do formulário.
Figura 6. Execução do formulário.

Este tipo de execução com diversos Selects no mesmo DataSet tem vantagens de performance em algumas situações. Por exemplo, precisamos abrir a conexão apenas uma vez, assim como dispensamos a declaração de vários DataAdapters e do Fill.

Conclusão

Saber identificar qual é o tipo de classe e método A ser utilizado para pesquisar dados no ADO.NET é fundamental e pode determinar o sucesso ou o fracasso da sua aplicação. É importante que você aplique os recursos utilizados neste artigo com o objetivo de medir a performance da sua aplicação antes e depois. Ou seja, caso a sua aplicação esteja diferente, faça uma medição do tempo de resposta atual e em seguida troque para as técnicas aprendidas aqui e reavalie o tempo de resposta.

Confira também

Capa da revista SQL magazine
Clique aqui para ler todos os artigos desta edição