Performance no ADO.NET - ExecuteScalar x DataSet?

Por Renato Haddad

Antes que você pense que estou ficando louco com esta comparação absurda, é importante entender exatamente o foco desta comparação para que você possa aproveitar o texto e os códigos e aplicar nas aplicações.

Todos sabemos que o ExecuteScalar é o meio mais rápido de se acessar e retornar qualquer dado contido em uma fonte de dados, por exemplo, uma tabela do banco de dados. O ExecuteScalar é usado somente em situações em que você precisa retornar uma linha e uma coluna. Atenção, eu escrevi uma linha e uma coluna, portanto, não queira fazer algo diferente disto. Veja alguns exemplos: capturar o status do cliente, a quantidade em estoque de um determinado produto, o saldo bancário, etc. O ExecuteScalar foi criado para isso e a performance é a melhor possível.

Já um DataSet é um espaço na memória RAM que pode conter diversas tabelas (DataTables) conforme a necessidade da aplicação. No entanto, para esta comparação vou criar apenas uma DataTable no DataSet. Com isso, podemos ter todos os dados de uma fonte de dados, por exemplo, um Select de uma tabela do banco de dados.

Agora sim, vamos ao objetivo do artigo. A situação é a seguinte: você precisa retornar o valor total (R$) do pedido que é calculado com a multiplicação de dois campos (preço e quantidade) em uma tabela. Se eu usar o ExecuteScalar será uma situação perfeita, no entanto, o foco é que preciso mostrar todos os produtos deste pedido, e como cada produto tem um preço e uma quantidade vendida, basta somar os valores e exibir o total aos usuário.

A questão principal é como efetuar esta pesquisa, e para isso, temos duas formas:

  1. Exibir os produtos com os respectivos dados e após fazer um ExecuteScalar (isso requer novamente um acesso ao banco de dados);

  2. Varrer toda a DataTable e somar os campos após o preenchimento dos valores. É importante entender que uma vez que a DataTable estiver na memória, dependendo da quantidade de dados, torna-se mais rápido que fazer um ExecuteScalar.

Este é o foco deste artigo, assim você aprende o ExecuteScalar e como varrer um DataTable. Com isso, você poderá comparar o tempo e definir qual é a melhor forma de aplicar isto na sua aplicação.

No ASP.Net, crie uma interface conforme a figura a seguir contendo um TextBox (txtPedido), um Button (btnScalar e Text: ExecuteScalar), dois Labels (lblTabela e lblTable) e dois DataGrids (gridPedidos com DataKeyField=OrderID e gridProdutos). O funcionamento se dará da seguinte forma: o usuário digita o número do pedido e quando clicar no botão ExecuteScalar, será pesquisado no banco de dados o valor total do pedido e exibido no lblTabela. Já o gridPedidos será preenchido no Page_Load e quando o usuário clicar no número do pedido (link na primeira coluna), serão exibidos no gridProdutos todos os produtos deste pedido e ao mesmo tempo, como a DataTable já estará na memória, será feita um looping para somar os valores e exibir no lblTable.

Cc517962.ADOExecuteScalar_01(pt-br,MSDN.10).png

Códigos

Como iremos acessar o banco de dados Northwind no SQL Server, declare o namespace para isso:

Imports System.Data.SqlClient

Faça o mesmo para o namespace que manipula um StringBuilder:

Imports System.Text

Antes do evento Page_Load, declare uma variável para usarmos como conexão, o qual toda a string de conexão com o SQL Server está armazenada na chave conexao no arquivo Web.Config.

Dim conexao As String = ConfigurationSettings.AppSettings("conexao")

Veja o código do evento Page_Load para carregar o gridPedidos com todos os pedidos contidos na tabela Orders. Note que a instrução SQL é passada como argumento para a função FillGrid. Em seguida a DataTable contida no DataSet retornado da função será a fonte de dados do gridPedidos. Isto é super simples de se aplicar e o gridPedidos será exibido ao usuário para que o mesmo selecione um pedido.

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
  Dim sql As String = "Select OrderID, CustomerID, ShipCity FROM Orders"
  gridPedidos.DataSource = FillGrid(sql).Tables(0)
  gridPedidos.DataBind()
End Sub

A função FillGrid recebe uma instrução SQL como argumento e retorna um DataSet. É estabelecida a conexão com o banco de dados, montado um DataAdapter com a respectiva tabela, preenchido a DataTable no DataSet e por fim, retornado o DataSet completo. Como não teremos mais de uma tabela, então, no preenchimento (Fill) nem declarei a tabela. Só por curiosidade, isso é mais rápido que declarar o nome da DataTable.

Private Function FillGrid(ByVal sql As String) As DataSet
    Dim conn As New SqlConnection(conexao)
    Dim adapter As New SqlDataAdapter(sql, conn)
    Dim ds As New DataSet
    conn.Open()
    adapter.Fill(ds)
    conn.Close()
    Return ds
End Function

Como o gridPedidos já está preenchido, o usuário poderá selecionar um pedido e, cabe a você capturar o número do pedido e disparar o código para montar o gridProdutos. Portanto, crie o seguinte código no evento ItemCommand do gridPedidos.

Private Sub gridPedidos_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles gridPedidos.ItemCommand

Faça um IF para verificar se o usuário clicou no respectivo link no grid.

If e.CommandName = "Select" Then

Capture o número do pedido clicado e armazene na variável pedido.

Dim pedido As Integer = gridPedidos.DataKeys(e.Item.ItemIndex)

Declare a instrução SQL com o StringBuilder, pois isto ficará mais rápido que concatenar strings. Leia toda esta instrução e note que há uma multiplicação de dois campos e um alias Total para eles.

       Dim sql As New StringBuilder
       sql.Append("SELECT [Order Details].OrderID, Products.ProductName, [Order Details].UnitPrice, ")
       sql.Append("[Order Details].Quantity, [Order Details].UnitPrice * [Order Details].Quantity AS Total ")
       sql.Append("FROM [Order Details] INNER JOIN Products ")
       sql.Append("ON [Order Details].ProductID = Products.ProductID ")
       sql.Append("WHERE [Order Details].OrderID = " & pedido)

Crie uma DataTable e chame a função FillGrid passando esta instrução SQL como argumento. Como o FillGrid retornar um DataSet, então use o Tables(0) para jogar a única Table no objeto DataTable.

       Dim tabela As New DataTable
       tabela = FillGrid(sql.ToString()).Tables(0)

Preencha o gridProdutos com esta DataTable.

       'preenche o grid
       gridProdutos.DataSource = tabela
       gridProdutos.DataBind()

Agora é a hora de varrer toda da DataTable e somar a coluna Total. Usei um For Each com o objeto DataRow em todas as linhas da tabela. A cada linha lida é acumulado o valor na variável somaTotal. É justamente nesta hora que você terá que avaliar se vale a pena fazer o que está demonstrado aqui ou usar o ExecuteScalar. Faça testes de performance, pois lembre-se que neste momento a DataTable está na memória.

       'calcula o valor
       Dim somaTotal As Double = 0
       For Each linha As DataRow In tabela.Rows()
           somaTotal += linha("Total")
       Next

Por fim, exiba o valor somado no lblTable já formatado.

       lblTable.Text = String.Format("Valor do pedido (by Table): {0:n2}", somaTotal)
    End If
End Sub

A outra forma é usar o método ExecuteScalar onde o retorno é um objeto com uma linha e uma coluna. Veja o código do botão btnScalar.

Private Sub btnScalar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnScalar.Click

Declare a string SQL com a função SUM para somar todos os campos contendo a multiplicação dos campos preço e quantidade. Note que o alias é o Total e a condição Where é o OrderID igual ao número do pedido digitado no txtPedido.

    Dim sql As New StringBuilder
    sql.Append("SELECT SUM(UnitPrice * Quantity) AS Total ")
    sql.Append("FROM [Order Details] ")
    sql.Append("WHERE OrderID = " & txtPedido.Text.Trim)

Abra a conexão e instancie o Command contendo a instrução SQL da respectiva conexão.

    Dim conn As New SqlConnection(conexao)
    Dim cmd As New SqlCommand(sql.ToString(), conn)
    conn.Open()

Exiba o resultado no lblTabela.

   lblTabela.Text = String.Format("Valor do pedido (by Scalar): {0:n2}", cmd.ExecuteScalar())
    conn.Close()
End Sub

Caso queira implementar paginação no gridPedidos, adicione o seguinte código.

Private Sub gridPedidos_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles gridPedidos.PageIndexChanged
    If e.NewPageIndex >= 0 Then
       gridPedidos.CurrentPageIndex = e.NewPageIndex
       gridPedidos.DataBind()
    End If
End Sub

Salve o projeto e execute-o no browser. Note que a primeira vez que a página é carregada, o gridPedidos é preenchido com todos os pedidos existentes na tabela Orders.

Cc517962.ADOExecuteScalar_02(pt-br,MSDN.10).png

Selecione um pedido clicando na primeira coluna e veja que é disparado o código que preenche o gridProdutos. É exatamente nesta hora que o valor total do pedido é calculado, e vale a pena você executar este código com o Debug, assim irá verificar que a tabela é preenchida (Fill) no DataSet apenas uma vez e o For Each varre a coluna Total somando todos os valores e exibe o resultado no lblTable.

Cc517962.ADOExecuteScalar_03(pt-br,MSDN.10).png

Por outro lado, é preciso executar o teste do ExecuteScalar. Para isso, digite o número do pedido no txtPedido e clique no botão para montar a respectiva string SQL e executar o ExecuteScalar.

Cc517962.ADOExecuteScalar_04(pt-br,MSDN.10).png

Conclusão

O uso do ADO.NET permite criar diversos meios de extrair dados de uma fonte de dados. Conhecer as classes e os métodos o ajudará a montar uma estrutura de testes para validar uma técnica e aplicar na sua aplicação. É importante ressaltar que o ExecuteScalar ou o DataSet pertence ao ADO.NET, e não ao ASP.NET. Portanto, você pode aplicar o que foi aprendido neste artigo em qualquer tipo de aplicação .NET, seja Pocket PC, ASP.NET, WinApplications, Console Applications, etc.

No stress, think ADO.NET!

Renato Haddad (rehaddad@msn.com) é MVP, editor da revista MSDN Magazine Brasil, ministra treinamentos e palestras sobre .NETe autor de diversos livros e treinamentos em CD multimídia de ASP.NET, SQL Reporting Services, Visual Studio .NET 2003 e Aplicações Móveis para celulares e Pocket PC, tanto no Brasil como em outros países da América Latina.