Clique aqui para ler este artigo em pdf  imagem_pdf.jpg

msdn02_capa.jpg

Clique aqui para ler todos os artigos desta edição

 

Técnicas de Preenchimento de DataTables do ADO.NET

Realizando a Sua Própria Análise

por JohnPapa

 

Uma das diferenças fundamentais entre o ADO.NET e o ADO clássico é a forma como os dados são apresentados. No ADO.NET, eles são representados nos objetos DataTable correspondentes, enquanto o ADO clássico representa os dados em objetos Recordset. Com o ADO clássico, o aplicativo típico recupera um único rowset e o armazena em um objeto Recordset. Ao usar objetos DataTable no ADO.NET, você pode armazenar diversos rowsets em diversos objetos DataTable, todos relacionados entre si e contidos dentro de um único DataSet. Por exemplo, você pode recuperar um rowset de clientes, um rowset de pedidos e um rowset de detalhamento dos pedidos e armazená-los em três objetos DataTable dentro de um único DataSet. Isto lhe dará uma grande flexibilidade para gerenciar seus dados.

Recentemente, recebi várias perguntas sobre qual a melhor técnica para recuperar dados e preencher um DataSet. Como o Microsoft® .NET Framework oferece uma variedade enorme de opções sobre como escrever o código, os desenvolvedores agora estudam atentamente essas opções com o intuito de fazer a melhor escolha para os seus aplicativos. Uma dessas escolhas consiste na forma de recuperar dados, relacioná-los, exibi-los e representá-los em um aplicativo. O conceito de montar os cenários antes de decidir como projetar um aplicativo não é novidade. A sua importância é bastante crítica, especialmente quando se trata de um novo modelo de desenvolvimento como o.NET Framework. Como os desenvolvedores se deparam com uma infinidade de maneiras de criar um aplicativo, os testes são mais importantes do que nunca.

Por essa razão, neste artigo vamos examinar uma maneira de avaliar diferentes decisões arquitetônicas, com ênfase na recuperação de dados. Examinarei diversas formas de recuperar dados nos DataTables e descreverei alguns testes de desempenho que executei, bem como quando e onde cada técnica pode ser útil aos aplicativos. Descreverei também alguns cenários que mostram como recuperar dados relacionais e gerenciá-los com eficácia. Você pode considerar alguns dos pontos apresentados a seguir como as melhores práticas, mas recomendo que também faça a sua própria análise.

 

Métodos de recuperação

Veja a seguir uma lista de algumas técnicas que podem ser utilizadas para carregar dados em objetos DataTable relacionados entre si dentro de um DataSet:

        Recupere todos os dados de uma consulta única associada (single joined query) em um único DataTable (veja a Listagem 1).

        Recupere cada rowset (três ao todo) de uma consulta em seu próprio DataTable usando subconsultas (veja a Listagem 2).

        Recupere cada rowset (três ao todo) de uma consulta em seu próprio DataTable usando associações (joins) (veja a Listagem 3).

        Recupere cada rowset (três ao todo) de uma consulta em seu próprio DataTable usando parâmetros.

 

Listagem 1. Recupere todos os dados em uma consulta única

--- Recupere informações de Customer, Order e Order Detail

SELECT    c.CustomerID,

    c.CompanyName,

    c.City,

    o.OrderID,

    o.OrderDate,

    od.ProductID,

    od.UnitPrice,

    od.Quantity

FROM    Customers c

    INNER JOIN Orders o ON c.CustomerID = o.CustomerID

    INNER JOIN [Order Details] od ON o.OrderID = od.OrderID

WHERE    (c.Country = 'USA')

ORDER BY

    o.CustomerID,

    o.OrderID,

    od.ProductID

 

Listagem 2. Usando subconsultas

--- Recupere informações de Customer

SELECT     CustomerID,

    CompanyName,

    City

FROM     Customers

WHERE    Country = 'USA'

ORDER BY

    CustomerID

 

--- Recupere informações de Order

SELECT     CustomerID,

    OrderID,

    OrderDate

FROM     Orders

WHERE     CustomerID IN    

    (

    SELECT CustomerID

    FROM Customers

    WHERE Country = 'USA'

    )

ORDER BY

    CustomerID,

    OrderID

 

--- Recupere informações de Order Detail

SELECT     OrderID,

    ProductID,

    UnitPrice,

    Quantity

FROM [Order Details]

WHERE OrderID IN

    (

    SELECT OrderID

    FROM Orders

    WHERE CustomerID IN

        (

        SELECT CustomerID

        FROM Customers

        WHERE Country = 'USA'

        )

    )

ORDER BY

    OrderID,

    ProductID

 

Listagem 3. Usando associações (joins)

--- Recupere informações de Customer

SELECT     CustomerID,

    CompanyName,

    City

FROM     Customers

WHERE    Country = 'USA'

ORDER BY

    CustomerID

 

--- Recupere informações de Order

SELECT     o.CustomerID,

    o.OrderID,

    o.OrderDate 

FROM     Customers c

    INNER JOIN Orders o ON c.CustomerID = o.CustomerID 

WHERE     c.Country = 'USA'

ORDER BY

    o.CustomerID,

    o.OrderID

 

--- Recupere informações de Order Detail

SELECT     od.OrderID,

    od.ProductID,

    od.UnitPrice,

    od.Quantity 

FROM     Customers c

    INNER JOIN Orders o ON c.CustomerID = o.CustomerID 

    INNER JOIN [Order Details] od ON o.OrderID = od.OrderID 

WHERE     c.Country = 'USA'

ORDER BY

    o.CustomerID,

    o.OrderID,

    od.ProductID

 

  Cada uma dessas técnicas recuperará todas as informações detalhadas sobre pedidos (Order) e clientes (Customers), de todos os clientes dentro dos E.U.A. Em seguida, os dados serão vinculados a um controle DataGrid em um Web Form para serem exibidos. Primeiramente, descreverei cada um dos métodos de recuperação. Em seguida, explicarei os testes realizados e, por fim, compartilharei os resultados do meu teste.

  O primeiro método recuperará todos os dados usando uma consulta única associada e preencherá um único DataTable. Desse modo, haverá apenas uma consulta, que se associará às tabelas de clientes (Customers), pedidos (Order) e detalhes de pedidos (Order Details) por meio de uma consulta SQL e retornará os campos de cada uma das tabelas exigidas. Em seguida, os resultados são armazenados em um DataTable dentro de um DataSet e vinculados ao controle DataGrid.

  O segundo método recupera todas as informações dos clientes americanos a partir de uma consulta SQL e as armazena em um DataTable. As informações sobre os pedidos são recuperadas através de uma consulta SQL que restringe os pedidos apenas aos clientes americanos, usando uma subconsulta na cláusula WHERE. Em seguida, as informações sobre os detalhes dos pedidos são recuperadas por meio de uma consulta SQL que restringe esses resultados apenas aos clientes americanos, usando uma subconsulta aninhada na cláusula WHERE.

  O terceiro método recupera todas as informações sobre clientes americanos a partir de uma consulta SQL e as armazena em um DataTable. Aqui, as informações dos pedidos são recuperadas através de uma consulta SQL e os pedidos são restritos apenas a clientes americanos por intermédio de sua associação à tabela de clientes e do uso da cláusula WHERE. Em seguida, as informações sobre detalhes de pedidos são recuperadas através de uma consulta SQL que obtém os clientes americanos associando-se às tabelas de clientes e pedidos e utilizando a cláusula WHERE que obtém o detalhamento dos pedidos apenas dos clientes americanos.

  O quarto método recupera todas as informações de clientes americanos a partir de uma consulta SQL e as armazena em um DataTable. Em seguida, é realizada uma consulta para cada cliente, um de cada vez, para recuperar todos os pedidos dos clientes. Essa consulta SQL possui um parâmetro para a coluna Orders.CustomerID que é desativado a cada vez que é feito um loop pelos clientes. Por fim, é executada uma consulta para cada detalhamento de pedido para recuperar todos os registros de detalhes de pedidos.

  Essa consulta SQL possui um parâmetro para a coluna [Order Details].OrderID que é desativado a cada vez que é feito um loop pelos pedidos. O problema desse método de recuperação é que a quantidade de consultas a serem realizadas pode ser enorme se comparada aos outros métodos. A consulta que recupera os clientes é realizada apenas uma vez, mas a que recupera os pedidos é realizada uma vez para cada cliente. Além disso, a consulta que recupera o detalhamento dos pedidos é realizada uma vez para cada pedido. Portanto, se existissem 10 clientes com 10 pedidos cada (desse modo, 100 pedidos no total), a consulta para recuperar os pedidos seria realizada 10 vezes e a consulta para recuperar o detalhamento de pedidos seria realizada 100 vezes. Essa técnica exigiria 111 consultas (1 para obter os clientes, 10 para os pedidos e 100 para os detalhes dos pedidos).

  Cada consulta é montada especificamente para obter os pedidos de determinado cliente ou os detalhes de determinado pedido. Assim, todas as consultas tendem a ser otimizadas. Devido ao número de consultas a serem realizadas e à quantidade de dados que se deslocavam de um lado a outro entre o banco de dados e o aplicativo nessas 111 viagens, os resultados do meu teste não foram surpresa: esse método foi o que levou, de longe, mais tempo para carregar os DataTables dentro do DataSet. No entanto, os resultados de desempenho que reuni nos testes dos outros três métodos de recuperação foram muito melhores e não ficaram distantes entre si.

 

O cenário de teste

O meu objetivo era encontrar a maneira mais eficiente de carregar um DataTable a partir de uma consulta ao banco de dados e exibir os resultados em um Web Form. O teste executa cada um dos três primeiros métodos de recuperação que acabei de analisar, pega os resultados e os carrega no conjunto de DataGrids correspondente. O teste realiza as consultas, carrega os dados em uma seqüência de DataTables dentro de um DataSet, vincula os dados a uma seqüência de DataGrids e, em seguida, repete-se 100 vezes. O processo é repetido 100 vezes para despertar quaisquer anomalias e para excluí-las com uma boa margem de certeza. O tempo total (em milissegundos) para executar a recuperação e vincular os dados aos DataGrids é somado e, em seguida, é feita a média sobre o conjunto de 100 repetições. Por fim, o tempo médio (em milissegundos) de execução das tarefas é exibido no Web Form, juntamente com os dados exibidos nos DataGrids. É claro que o teste não é perfeito, mas como todos os três métodos de recuperação passaram pelo mesmo teste no mesmo ambiente, a comparação dos resultados de cada método de recuperação é certamente válida.

  A Listagem 4 mostra o HTML utilizado pelo Web Form para exibir os resultados do teste, bem como os rowsets de cada método de recuperação em diversos DataGrids. O controle Label é usado para exibir o tempo médio (em milissegundos) que cada teste leva para ser executado. Os controles DataGrid são usados para exibir os rowsets da última interação de cada teste de cada método de recuperação.

  A estrutura do código de teste é uma abordagem tradicional em camadas, um pouco ineficiente para os nossos objetivos, mas ainda assim bastante apropriada aos aplicativos em camadas. A página WebForm1.aspx manipula a exibição dos dados e os resultados do teste. Ela possui um arquivo Code-Behind denominado WebForm1.aspx.cs, que contém todos os códigos (para todos os eventos) que interagem com WebForm1.aspx. É nesse Code-Behind que o teste é iniciado, cronometrado e divulgado. O Code-Behind chama a classe personalizada Customer.cs, que contém três métodos públicos dignos de nota: GetData_Using1Query, GetData_UsingSeparateQueriesUsingJoins e GetData_UsingSeparateQueriesWithSubqueries.

 

Listagem 4. Exibindo os resultados do teste

    

code01.jpg

 

  Cada método representa uma das técnicas de recuperação. Todos eles aceitam o nome de usuário e a senha do banco de dados Northwind e criam um DataSet vazio para armazenar os resultados. Em seguida, os métodos instanciam uma conexão ao banco de dados usando o objeto SqlConnection. A partir daí, os diferentes métodos divergem, e cada método define suas instruções SQL de acordo com seu objetivo. Conforme mostrado na Listagem 5, o método GetData_UsingSeparateQueriesUsingJoins define a instrução SQL para recuperar os CustomerID e CompanyName dos clientes americanos. Uma nova instância de um objeto SqlCommand é criada e inicializada com a instrução SQL por meio do construtor de argumento único do objeto SqlCommand. Em seguida, o CommandType é definido e o objeto SqlConnection é vinculado ao objeto SqlCommand.

Listagem 5. Usando diferentes consultas (joins)

//-----------------------------------------------------------------------

// public Method

// Overloaded: No

// Parameters: string sUserName e string sPassword

// Return Value: DataSet

//-----------------------------------------------------------------------

public DataSet GetData_UsingSeparateQueriesUsingJoins(string sUserName,

   string sPassword)

{

    this.m_sMethodName = "[public DataSet      

                         GetData_UsingSeparateQueriesUsingJoins"  +

                         " (string sUserName, string sPassword)]";

 

    DataSet oDS = new DataSet();

    string sSQL;

 

    try

    {

        //---------------------------------------------------------------

        //-- Configura a conexão

        //---------------------------------------------------------------

        string sConn = "Data Source=(local);Initial

            Catalog=northwind;User ID=" +

            "sUserName + ";Password=" + sPassword + ";";

        SqlConnection oCn = new SqlConnection(sConn);

 

        //---------------------------------------------------------------

        //-- Define o SELECT Command para Clientes (Customer)

        //---------------------------------------------------------------

        sSQL = "SELECT CustomerID, CompanyName, City FROM Customers " +

                " WHERE Country = 'USA' " +

                " ORDER BY CustomerID";

        SqlCommand oSelCmd_Customer = new SqlCommand(sSQL);

        oSelCmd_Customer.CommandType = CommandType.Text;

        oSelCmd_Customer.Connection = oCn;

 

        //---------------------------------------------------------------

        //-- Define o SELECT Command para Pedidos (Orders)

        //---------------------------------------------------------------

        sSQL = "SELECT o.CustomerID, o.OrderID, o.OrderDate  " +

            " FROM Customers c INNER JOIN Orders o ON c.CustomerID =

                o.CustomerID  " +

            " WHERE c.Country = 'USA' " +

            " ORDER BY o.CustomerID, o.OrderID";

        SqlCommand oSelCmd_Order = new SqlCommand(sSQL);

        oSelCmd_Order.CommandType = CommandType.Text;

        oSelCmd_Order.Connection = oCn;

        //---------------------------------------------------------------

        //-- Define o SELECT Command para Detalhes do Pedido (Order Details)

        //---------------------------------------------------------------

        sSQL = "SELECT od.OrderID, od.ProductID, od.UnitPrice,

            od.Quantity  " +

            " FROM Customers c INNER JOIN Orders o ON c.CustomerID =

                o.CustomerID  " +                "    INNER JOIN [Order

                Details] od ON o.OrderID = od.OrderID  " +

            " WHERE c.Country = 'USA' " +

            " ORDER BY o.CustomerID, o.OrderID, od.ProductID";

        SqlCommand oSelCmd_OrderDetail = new SqlCommand(sSQL);

        oSelCmd_OrderDetail.CommandType = CommandType.Text;

        oSelCmd_OrderDetail.Connection = oCn;

 

        //---------------------------------------------------------------

        //-- Cria e instancia os DataAdapters

        //---------------------------------------------------------------

        SqlDataAdapter oDA_Customer = new SqlDataAdapter();

        SqlDataAdapter oDA_Order = new SqlDataAdapter();

        SqlDataAdapter oDA_OrderDetail = new SqlDataAdapter();

        oDA_Customer.SelectCommand = oSelCmd_Customer;

        oDA_Order.SelectCommand = oSelCmd_Order;

        oDA_OrderDetail.SelectCommand = oSelCmd_OrderDetail;

 

        //---------------------------------------------------------------

        //-- Pega os dados

        //---------------------------------------------------------------

        oDA_Customer.Fill(oDS, "Customer");

        oDA_Order.Fill(oDS, "Order");

        oDA_OrderDetail.Fill(oDS, "OrderDetail");

 

        //---------------------------------------------------------------

        //-- Dispose dos objetos

        //---------------------------------------------------------------

        oCn.Dispose();

        oSelCmd_Customer.Dispose();

        oSelCmd_Order.Dispose();

        oSelCmd_OrderDetail.Dispose();

        oDA_Customer.Dispose();

        oDA_Order.Dispose();

        oDA_OrderDetail.Dispose();

    }

    catch (Exception ex)

    {

        //---------------------------------------------------------------

        //-- Throw caso ocorra uma Exception

        //---------------------------------------------------------------

        LogAndThrow(ex, sUserName, this.m_sClassName, this.m_sMethodName,

            "my err description");

    }

    finally

    {

    }

 

    //--------------------------------------------------------------------

    //-- Retorna o DataSet

    //--------------------------------------------------------------------

    return oDS;

}

 

  Esse processo se repete para as outras instruções SQL que recuperam os pedidos e os detalhes dos pedidos. O método utiliza três objetos SqlCommand e três objetos SqlDataAdapter: um para os clientes, um para os pedidos e um para os detalhes dos pedidos. Depois que todos os objetos SqlCommand forem instanciados, tiverem definidas suas instruções SQL e estiverem vinculados ao SqlConnection, são criados os três objetos SqlDataAdapter. Os objetos SqlCommand são então atribuídos à propriedade SelectCommand de seus objetos SqlDataAdapter correspondentes. Por exemplo, o objeto SqlCommand que recupera os clientes (oSelCmd_Customer) é atribuído à propriedade SelectCommand do objeto SqlDataAdapter (oDA_Customer), e assim por diante. O SqlDataAdapter possui quatro propriedades de objeto de comando que correspondem a cada uma das quatro principais instruções SQL de recuperação e manipulação: InsertCommand, UpdateCommand, DeleteCommand e SelectCommand. Como esse código destina-se apenas a recuperar dados, só será necessário definir o SelectCommand.

Em seguida, é chamado o método Fill de cada um dos três objetos SqlDataAdapter, o que resulta em uma viagem ao banco de dados e na execução de cada uma das instruções SQL, separadamente. Os dados são então retornados ao DataSet criado no início do método público GetData_UsingSeparateQueriesUsingJoins e armazenados nos DataTables Customer, Order e OrderDetail dentro do DataSet. Em seguida, o código relaciona os objetos DataTable entre si com os objetos DataRelation. Isso só pode ser feito nos dois métodos que recuperam três rowsets separados, já que o outro método recupera apenas um único rowset.

O fragmento de código mostrado na Listagem 6 faz 100 loops para recuperar os dados da classe Customer.  Cada DataTable é vinculado a um DataGrid e o tempo decorrido (em milissegundos) é totalizado. Por fim, a média do tempo decorrido é calculada e exibida em um Label no Web Form.

Listagem 6. Repetindo 100 vezes a recuperação de dados

lblMessage.Text += "GetData_UsingSeparateQueriesUsingJoins
";

fAvg = 0;

for(i = 0; i < 100; i++)

{

    x = System.DateTime.Now;

    oDS = oCustomer.GetData_UsingSeparateQueriesUsingJoins("sa", "");

    grdCustomer3.DataSource = oDS.Tables["Customer"].DefaultView;

    grdCustomer3.DataBind();

    grdOrder3.DataSource = oDS.Tables["Order"].DefaultView;

    grdOrder3.DataBind();

    grdOrderDetail3.DataSource = oDS.Tables["OrderDetail"].DefaultView;

    grdOrderDetail3.DataBind();

    y = System.DateTime.Now;

    z = y - x;

    fAvg += z.Milliseconds;

}

lblMessage.Text += "Average Ms = " + (fAvg / i).ToString() ;

lblMessage.Text += "

";

 

 

Resultados do teste

Após realizar esse teste várias vezes, os resultados mostraram que retornar os dados em uma consulta única associada era, consistentemente, a técnica com desempenho menos eficiente. O lado esquerdo da Tabela 1 mostra os resultados do teste dos clientes americanos, seus pedidos e detalhamentos de pedidos. Reunir esses dados com uma consulta única associada levou três milissegundos a mais que as outras técnicas, o que resultou em aproximadamente 16% de retardo em relação à técnica imediatamente mais próxima. Considere que retornar os dados em uma única consulta associada (joined query) significa reunir os dados e recuperá-los, tendo uma significativa redundância.

 Por exemplo, o nome da empresa "Save-a-lot Markets" é repetido mais de 50 vezes no rowset, uma vez para cada pedido e seu respectivo detalhamento de pedido. São muitos dados a serem armazenados, transportados e submetidos a um navegador. Pode não parecer muito, mas com certeza é mais do que se o nome da empresa do cliente fosse retornado apenas uma vez. Embora três milissegundos não seja muito tempo, lembre-se de que, quando esses testes são transportados para cenários amplos e reais, o desempenho pode realmente fazer a diferença.

 

Tabela 1. Resultado dos testes em milissegundos

Consulta

Clientes Americanos

Todos os clientes

Subconsulta

18.52

123.47

Associação

18.81

138.56

Consulta Única

21.87

156.02

 

Por exemplo, apesar de o banco de dados Northwind não armazenar tantas informações como em um cenário real, vamos ver o que aconteceria se todos os clientes fossem recuperados — e não apenas os americanos (veja o lado direito da Tabela 1). Repare que ainda existem diferenças nas técnicas, já que a consulta única leva aproximadamente 12% de tempo a mais do que a próxima técnica mais rápida.

Lembre-se de que uma consulta única realiza apenas uma consulta, enquanto as outras técnicas realizam três consultas cada. Apesar de realizar mais consultas, as outras técnicas são sistematicamente concluídas mais rápido que a consulta única associada. Esse comportamento demonstra que, na maioria dos cenários, realizar diversas consultas otimizadas é melhor que realizar uma consulta única não-otimizada. Isso poderia significar também que as consultas propriamente ditas são irrelevantes e que a renderização e o transporte dos dados é que causam o retardo. Esse retardo se deve ao fato de que a consulta única retorna mais dados do que três consultas separadas.

Por outro lado, utilizar uma consulta única associada (joined query) pode ser muito útil em determinados momentos. Um deles é quando o aplicativo exige que os dados sejam exibidos em uma única grade não-hierárquica. Na maioria das vezes, eu executo uma consulta única quando quero exibir os dados em um único local (como em um DataGrid). No entanto, quando um aplicativo requer leitura e gravação em várias tabelas de um banco de dados subjacente, prefiro usar consultas separadas, que carreguem os objetos DataTable correspondentes também em separado, devido à flexibilidade oferecida no ADO.NET.

Esse teste poderia ser alterado de diversas formas, e uma delas seria omitir totalmente os objetos DataRelation. Executei alguns testes sem os objetos DataRelation e descobri que utilizá-los alterava os resultados em aproximadamente um milissegundo em relação aos resultados da coluna de clientes americanos (mostrado na Tabela 1), para cada um dos métodos que executaram as três consultas. (Obviamente, o método que realiza a consulta única associada (single joined query)  não requer um DataRelation, por isso ele não foi afetado.) Assim, remover o DataRelation melhorou o desempenho da execução de três consultas separadas que usavam subconsultas ou associações, por causa do tempo que o ADO.NET leva para aplicar as relações.

Você poderia concluir que realizar uma consulta única associada é mais demorado do que realizar consultas separadas, e que o retardo ocorria principalmente durante a associação dos dados. Se você der uma olhada no código do método que executa as três consultas utilizando associações, notará que são as mesmas associações utilizadas na consulta única. Na verdade, a maior diferença está nos campos a serem retornados. A consulta única retorna todos os campos de uma única instrução SQL, enquanto as consultas separadas retornam apenas os campos necessários para cada consulta. A consulta de clientes somente vai até a tabela de clientes e obtém os campos. A consulta de pedidos vai até as tabelas de clientes e pedidos e obtém apenas os campos dos pedidos. A consulta de detalhes dos pedidos vai a todas as três tabelas, mas obtém apenas os campos de detalhes dos pedidos. Levando em conta todas essas informações, esta seqüência de testes mostra que realmente as associações afetaram de alguma forma o desempenho, mas os campos que estão sendo retornados (e sua redundância) provavelmente afetaram muito mais.

O melhor método de desempenho foi o das três consultas que utilizaram subconsultas. O código dessa técnica pode se tornar um tanto incômodo, já que a consulta de terceiro nível precisa executar uma subconsulta que execute outra subconsulta para obter seus resultados. Como os resultados das três consultas que utilizaram subconsultas estiveram muito próximos das consultas que utilizaram associações, prefiro a última técnica, já que o código é bem mais fácil de ler e manter. Afinal, o desempenho é apenas um dos fatores (embora o mais importante) a ser considerado para uma implementação de aplicativos apropriada.  Capacidade de manutenção, escalabilidade, flexibilidade, confiabilidade e consistência também são muito importantes.

 

Conclusão

O código que forneci demonstra como é fácil recuperar dados em objetos DataSet ADO.NET por meio de diferentes técnicas. Fique à vontade para fazer o download do código de teste desta coluna e para modificá-lo enquanto explora outros cenários de teste. Lembre-se de analisar cuidadosamente a maneira como você irá projetar seus aplicativos. Executar esses testes revelou não apenas o desempenho de vários métodos como também a facilidade de escrever o código, modificá-lo, entendê-lo e levá-lo para o próximo nível. Recomendo que, ao desenvolver qualquer aplicativo, você separe um tempo para realizar cenários de teste para as diferentes partes móveis de seu aplicativo, como recuperação de dados, relatórios, modificações, geração de tela, processamento de eventos e outros aspectos da aplicação. Se o aplicativo foi bem testado, ele será mais sólido (robusto) e você terá menos surpresas.