Estratégia de Acesso a Dados no ADO.NET e SQL

por John Papa

Este artigo discuteEste artigo usa as seguintes tecnologias:

Pooling de conexões

Stored Procedures

Declaração de parâmetros

ExecuteScalar, ExecuteReader, DataSet

C# e SQL

Download:

Chapéu
ADO.NET e SQL

 

Quando sua meta for uma solução empresarial eficiente e escalável, você precisará desenvolver uma estratégia eficiente de acesso a dados. Você não pode apenas testar suas máquinas de produção e depender dos resultados. Embora um aplicativo possa exibir excelentes tempos de resposta para alguns usuários, é possível que haja problemas de desempenho quando a carga aumenta. Por isso, quando sou solicitado a executar revisões de código e de arquitetura, procuro vários modelos de codificação. Neste artigo, compartilharei algumas delas, já que estão relacionadas ao acesso a dados com o ADO.NET. Discutirei maneiras eficientes de implementar as conexões de banco de dados do ADO.NET de modo a aproveitar as vantagens dos pools de conexão e apresentarei dicas sobre como usar os vários objetos ADO.NET para abrir e fechar conexões e para gerenciar o estado da conexão para você. Discutirei também as conexões e o DataAdapter e DataReader, bem como stored procedures, consultas parametrizadas e ataques de injeção SQL.

Conexões ADO.NET

As conexões com os banco de dados são geralmente uma preocupação tardia, o que é surpreendente se considerarmos como elas são essenciais e como podem se tornar facilmente uma fonte de problemas e gargalos. Uma maneira de reduzir o número de conexões e o uso concomitante de seus recursos consiste em reutilizar as conexões por meio do pool de conexões. Para ser capaz de reutilizar uma conexão a partir de um pool, a string de conexão precisa corresponder à string de uma conexão de pool disponível. Cada pool de conexão é associado a uma string de conexão distinta, usando um algoritmo de correspondência exata. Se nenhuma correspondência exata for encontrada, será criada uma nova conexão, a qual será posteriormente incluída no pool.

A Listagem 1 mostra três exemplos de código que abrem uma conexão com o mesmo banco de dados, usando dois usuários diferentes. A primeira conexão é acessada pelo usuário SQL Server™ denominado TestUser1. Presumindo que não haja nenhuma conexão disponível no pool, essa conexão será criada do nada. Posteriormente, essa conexão será fechada e enviada para o pool de conexões. A segunda conexão é acessada por outro usuário SQL Server. A conexão desativada no pool não tem uma correspondência de string de conexão exata, já que os usuários são diferentes. Assim, a segunda conexão é criada do nada, fechada e enviada ao pool de conexões. Em seguida, é aberta uma terceira conexão pelo usuário SQL Server, denominada TestUser1. Existe uma conexão no pool que corresponde exatamente à string de conexão, por isso a conexão é recuperada do pool.

Listagem 1 Pooling de conexões

using(SqlConnection oCn = new SqlConnection(
    "Server=(local);Database=Pubs;User ID=TestUser1;Password=foo1;")) {
    oCn.Open();
    ...
}

using(SqlConnection oCn = new SqlConnection(
    "Server=(local);Database=Pubs;User ID=TestUser2;Password=foo2;")) {
    oCn.Open(); 
    ...
}

using(SqlConnection oCn = new SqlConnection(
    "Server=(local);Database=Pubs;User ID=TestUser1;Password=foo1;")) {
    oCn.Open();
    ...
}

Você pode tirar proveito do pooling de conexões utilizando uma conta de serviço para seu aplicativo. Por exemplo, pode criar uma conta de usuário SQL Server única que poderá ser usada pelo aplicativo para acessar o banco de dados. Isso lhe permitirá aproveitar as vantagens do pooling de conexões, já que as strings de conexão vão sempre corresponder. Esta é uma boa opção da perspectiva da escalabilidade. No entanto, se quiser maior controle sobre a segurança, considere o uso da autenticação Windows®.

Início da páginaInício da página

Abrir Mais Tarde, Fechar Mais Cedo

Outro item relacionado à conexão que costumo avaliar quando executo uma revisão de código é se a conexão será ou não mantida aberta pelo período de tempo mais curto que ela for necessária. A regra básica é manter a conexão aberta somente pelo tempo que você precisa dela. Você só deverá abrir uma conexão imediatamente antes de acessar o banco de dados, e fechá-la assim que terminar de acessar o banco de dados. Tome cuidado com qualquer código que não seja relacionado ao banco de dados e que seja executado enquanto a conexão estiver aberta. As conexões mantêm abertos recursos valiosos para o banco de dados, consomem memória e podem bloquear dados, o que faz com que outras pesquisas se tornem mais lentas. Por isso, é melhor abrir as conexões mais tarde e fechá-las o mais cedo possível.

Um dos melhores recursos do ADO.NET é que os métodos Fill e Update do objeto DataAdapter podem abrir e fechar automaticamente uma conexão (consulte a Listagem 2). A vantagem disso é que não é necessário abrir explicitamente a conexão porque o DataAdapter a abre por você imediatamente antes de executar o comando SQL no banco de dados e, em seguida, fecha-a imediatamente após.

Listagem 2 O Método Fill Abre, É Executado e, Depois, Fecha

using (SqlConnection oCn = new SqlConnection(sConnString)) 
{
    string sProcName = "prGet_Products";
    using (SqlCommand oSelCmd = new SqlCommand(sProcName, oCn)) 
{
        oSelCmd.CommandType = CommandType.StoredProcedure;

        oDa.SelectCommand = oSelCmd;
        oDa.Fill(oDs); 
    }
}

Além disso, recomenda-se evitar passar uma conexão aberta entre os métodos sempre que possível. Em vez disso, você pode querer usufruir o pooling de conexões e fechar a primeira conexão, chamar o método sem passar a conexão e, em seguida, abrir uma nova conexão dentro do método chamado.

Assim como o objeto Connection, o objeto DataReader deverá ser sempre fechado após ter sido usado. Ao utilizar um DataReader, procuro ver se o método ExecuteReader do objeto Command possui seu CommandBehavior definido como CloseConnection. Isso assegurará que a conexão seja automaticamente fechada quando o DataReader for fechado. Caso deseje retornar vários rowsets, você deve omitir o CommandBehavior para que ele use sua configuração padrão, o que permite que vários rowsets sejam retornados e acessados. A Listagem 3 demonstra que Connection será automaticamente fechada quando DataReader for fechado.

Listagem 3 Fechando Automaticamente a Conexão

oCn.Open();
SqlDataReader oDr = oCmd.ExecuteReader(CommandBehavior.CloseConnection);
while (oDr.Read())
{
    Debug.WriteLine(oDr[1].ToString());
}
Debug.WriteLine("Connection is " + oCn.State);
oDr.Close();
Debug.WriteLine("Connection is " + oCn.State);

Um sinal de que a conexão está sendo mantida aberta mais tempo que o necessário é que você encontra um código que verifica se o ConnectionState da conexão está aberto ou fechado:

if (oCn.State == ConnectionState.Closed) {
    oCn.Open();
}

A presença desse tipo de código indica que a conexão deverá ser aberta em alguns casos quando ela chegar a este ponto. Uma exceção a essa diretriz que poderia fazer com que você mantivesse as conexões abertas por mais tempo é quando você precisa implementar uma transação que abrange vários comandos.

Se você não fechar explicitamente a conexão e, em vez disso, aguardar até que o coletor de lixo libere o recurso, a conexão não será liberada de volta para o pool de conexão até que o coletor de lixo a solicite, e você não tem certeza de quando isso vai acontecer.

Início da páginaInício da página

Retornando uma Única Linha ou Coluna

O objeto ADO.NET Command expõe vários métodos Execute. Os métodos ExecuteReader e ExecuteXmlReader são capazes de retornar várias linhas e colunas a um DataReader e XmlReader, respectivamente. O método Fill do DataAdapter também pode retornar várias linhas e colunas para um DataSet.

No entanto, quando você quiser executar uma consulta de ação e não quiser retornar nenhum dado, use o método ExecuteNonQuery do objeto Command. Ele executará esse comando e não se preocupará com nenhum overhead envolvido no retorno dos dados. Esse método é ideal para atualizações e exclusões quando você quer que a instrução SQL seja executada.

Caso deseje recuperar um único valor, os métodos ExecuteReader, ExecuteXmlReader e Fill são geralmente excessivos. Se usar o método Fill do DataAdapter para preencher um DataSet com uma única coluna e uma única linha, você também obterá todos os metadados e o overhead que vem com o objeto DataSet mais avançado. Seria mais eficaz usar o método ExecuteScalar do objeto Command. Você poderia executar a mesma instrução SELECT e recuperar o valor único para uma variável. Para demonstrar, observe os dois exemplos da Listagem 4 que utilizam ambas as técnicas para executar uma instrução SQL que retorna um valor único.

Listagem 4 Retornando um Valor Único

//Usando SqlCommand.ExecuteScalar
private void UseExecuteScalar() {
    string sConnString = 
        "Server=(local);Database=Northwind;Integrated Security=True;";
    string sSQL = 
        "SELECT CompanyName FROM Customers WHERE CustomerID = 'ALFKI'";
    using (SqlConnection oCn = new SqlConnection(sConnString)) {    
        using (SqlCommand oSelCmd = new SqlCommand(sSQL, oCn)) {
            oCn.Open();
            string sCompany = oSelCmd.ExecuteScalar().ToString();
            oCn.Close();
        }
    }
}

//Usando SqlDataAdapter.Fill
private void UseFill() {
    string sConnString = 
        "Server=(local);Database=Northwind;Integrated Security=True;";
    string sSQL = 
        "SELECT CompanyName FROM Customers WHERE CustomerID = 'ALFKI'";
    using (SqlConnection oCn = new SqlConnection(sConnString)) {
        using (SqlCommand oSelCmd = new SqlCommand(sSQL, oCn)) {
            SqlDataAdapter oDa = new SqlDataAdapter();
            DataSet oDs = new DataSet();
            oDa.SelectCommand = oSelCmd;
            oDa.Fill(oDs);
        }
    }
}

Se quiser retornar mais de uma coluna a partir de uma única linha, use o método Fill do DataAdapter para preencher um DataSet. Como alternativa, em vez de retornar um rowset a partir de um stored procedure, você poderia retornar os valores em parâmetros de saída. Por exemplo, em vez de usar esta stored procedure.

CREATE PROCEDURE prGet_CustomerRowSet
    @sCustomerID NCHAR(5)
AS
    SELECT CompanyName, ContactName, City 
    FROM Customers 
    WHERE CustomerID = @sCustomerID
GO
você pode querer usar o stored procedure da Listagem 5. 

Listagem 5 Aproveitando as Vantagens dos Parâmetros de Saída
CREATE PROCEDURE prGet_Customer
    @sCustomerID NCHAR(5),
    @sCompanyName NVARCHAR(40) OUTPUT, 
    @sContactName NVARCHAR(30) OUTPUT, 
    @sCity NVARCHAR(15) OUTPUT
AS
    SELECT @sCompanyName = CompanyName, 
           @sContactName = ContactName, 
           @sCity = City
    FROM Customers 
    WHERE CustomerID = @sCustomerID
GO

Quando chamado a partir do ADO.NET, a primeira stored procedure pode ser usada para preencher um DataSet a partir de um DataAdapter. O DataSet incluiria os dados e todos os metadados de suporte como overhead. A segunda stored procedure pode ser chamada a partir do ADO.NET por meio de um objeto Command. Seus valores podem ser recuperados por meio da coleção de parâmetros do objeto Command. A stored procedure prGet_Customer é mais eficaz, já que não precisa passar um rowset nem o código de cliente precisa comportar o peso do overhead extra que vem com o DataSet. O código na Listagem 6 poderia ser usado para recuperar os parâmetros de saída a partir da segunda stored procedure (prGet_Customer). Os três valores podem ser armazenados em variáveis que requerem menos de 100 bytes. Por outro lado, se você retornar os mesmos três valores em um DataSet a partir de um DataAdapter usando a primeira stored procedure, o DataSet e seu esquema terá quase 1.000 bytes de tamanho. Se tudo que você precisa é um punhado de valores a partir de uma única linha, o uso de ExecuteScalar (para 1 valor) ou de parâmetros de saída permitirá uma execução mais rápida do recuperar um rowset em um DataSet e, em seguida, enviar esses dados para outra camada de aplicativo.

Listagem 6 Recuperando Valores Via Parâmetros de Saída

private void GetOutputValues()
{
    string sConnString = 
        "Server=(local);Database=Northwind;Integrated Security=True;";
    string sProc = "prGet_Customer";
    using (SqlConnection oCn = new SqlConnection(sConnString))
    {
        using (SqlCommand oCmd = new SqlCommand(sProc, oCn))
        {
            oCn.Open();
            oCmd.CommandType = CommandType.StoredProcedure;

            oCmd.Parameters.Add("@sCustomerID", SqlDbType.NChar, 5);
            oCmd.Parameters["@sCustomerID"].Value = "ALFKI";

            oCmd.Parameters.Add("@sCompanyName", SqlDbType.NVarChar, 40);
            oCmd.Parameters["@sCompanyName"].Direction = 
                ParameterDirection.Output;

            oCmd.Parameters.Add("@sContactName", SqlDbType.NVarChar, 30);
            oCmd.Parameters["@sContactName"].Direction = 
                ParameterDirection.Output;

            oCmd.Parameters.Add("@sCity", SqlDbType.NVarChar, 15);
            oCmd.Parameters["@sCity"].Direction = 
                ParameterDirection.Output;

            oCmd.ExecuteNonQuery();
            oCn.Close();

            string sCompanyName = 
                oCmd.Parameters["@sCompanyName"].Value.ToString();
            string sContacName = 
                oCmd.Parameters["@sContactName"].Value.ToString();
            string sCity = oCmd.Parameters["@sCity"].Value.ToString();
        }
    }
}
Início da páginaInício da página

Consultas Ineficazes

Freqüentemente, quando examino o SQL e as stored procedures, percebo que são recuperados mais dados dos que os efetivamente utilizados. Uma instrução SELECT deveria retornar apenas as colunas que fossem ser usadas, possivelmente para fins de exibição ou para alguma lógica de negócios. Por exemplo, suponha que eu retorne 100 linhas de dados e você recupere duas colunas inteiras extras que não sejam usadas em nenhum lugar. Cada coluna de inteiros tem 4 bytes, por isso retorno 8 bytes por linha ou 800 bytes extras de dados não utilizados. Em seguida, quando inseridos em um DataSet (que pode ser passado para outra camada de aplicativo), o XML e os metadados de suporte necessários para as colunas extras são adicionados a esse total.

Com isso, estou agora retornando dados supérfluos do servidor do banco de dados para meu servidor de lógica de negócios e, em seguida, passando dados extras como XML para serviços que podem muito bem existir em outras máquinas da rede. Não raro ouço dizer que o tamanho dos dados é tão pequeno que não se observa nenhuma diferença no desempenho. Evidentemente, esse argumento poderá ser válido quando houver apenas um único usuário acionando o aplicativo. No entanto, quando você tentar escalar o aplicativo para milhares de usuários, o envio de dados desnecessários pela rede poderá prejudicar o desempenho.

Outro problema é quando uma instrução SELECT recupera mais linhas do que ela precisa. Por exemplo, retornar 100 linhas é um exagero quando você exibirá apenas as dez primeiras. É melhor ajustar a instrução SELECT para recuperar apenas os dez primeiros registros, usando a palavra-chave TOP.

O objeto CommandBuilder é geralmente usado no código de demonstração para mostrar como ele pode automaticamente compreender como executar instruções INSERT, UPDATE e DELETE. Ele utiliza a instrução SELECT de um DataAdapter e tenta gerar os outros comandos por você. Isto será mais lento do que se você codificasse os comandos por conta própria, porque o CommandBuilder precisa compreender o processo (o que leva tempo) e, geralmente, cria consultas menos eficientes do que você criaria. Recomendo que você evite totalmente o objeto CommandBuilder em tempo de execução.

Outro ponto-chave que costumo procurar nas revisões do código SQL e das stored procedures consiste nos joins excessivos. Eles são freqüentemente encontrados nas instruções SELECT quando uma tabela tiver sido necessária em um dado momento para recuperar uma coluna, tiver sido removida após isso, mas a associação com a tabela permanecer. Também procuro por associações extras, particularmente durante a associação de uma tabela cuja coluna já possa ser acessada sem precisar usar uma associação.

Início da páginaInício da página

Comandos e Parâmetros

Quando uma instrução SQL é executada, o banco de dados precisa gerar um plano de execução para ela. Se essa instrução SQL for executada repetidamente, cada vez que ela for executada o banco de dados precisará gerar novamente o plano de execução da consulta. Nos casos em que o SQL é executado com muita freqüência, mover o SQL para uma stored procedure pode oferecer melhor desempenho (sem falar da melhoria de segurança). Na primeira vez que uma stored procedure é executada, o banco de dados gera um plano de execução de consulta, armazena esse plano no cache da procedure e, em seguida, executa a stored procedure. Nas chamadas subseqüentes da stored procedure, o mecanismo do banco de dados só precisa capturar o plano de consulta do cache da procedure e executar novamente a stored procedure. Com isso, ele evita a etapa de desenvolvimento do plano de consulta nas chamadas subseqüentes. Para melhor desempenho, sugiro sempre utilizar as stored procedures quando elas forem uma opção disponível em uma arquitetura do banco de dados.

Outra opção é executar uma instrução SQL usando o objeto SqlCommand e chamando seu método Prepare. Se o provedor de dados suportar esse processo, chamar o método Prepare informa ao provedor de dados que se prepare para que essa instrução SQL seja executada várias vezes. O plano de execução de consulta da instrução SQL será então armazenado pelo banco de dados de modo que as execuções subseqüentes sejam executadas mais rapidamente. No entanto, a execução do método Prepare só deverá ser usada nas situações em que o SQL será executado várias vezes. O processo de preparar a instrução SQL exige um pouco de overhead e, assim, se as instruções forem executadas apenas uma vez, o desempenho cairá. A Listagem 7 demonstra como uma instrução UPDATE pode ser preparada e executada duas vezes.

Listagem 7 Executando um Comando Preparado

private void CallPreparedCmd() {
    string sConnString = 
        "Server=(local);Database=Northwind;Integrated Security=True;";
    string sSQL = 
        "UPDATE Customers SET City=@sCity WHERE CustomerID=@sCustomerID";
    using (SqlConnection oCn = new SqlConnection(sConnString)) {
        using (SqlCommand oCmd = new SqlCommand(sSQL, oCn)) {
            oCmd.CommandType = CommandType.Text;

            oCmd.Parameters.Add("@sCustomerID", SqlDbType.NChar, 5);
            oCmd.Parameters.Add("@sCity", SqlDbType.NVarChar, 15);

            oCn.Open();
            oCmd.Prepare();

            oCmd.Parameters["@sCustomerID"].Value = "ALFKI";
            oCmd.Parameters["@sCity"].Value = "Berlin2";
            oCmd.ExecuteNonQuery();

            oCmd.Parameters["@sCustomerID"].Value = "CHOPS";
            oCmd.Parameters["@sCity"].Value = "Bern2";
            oCmd.ExecuteNonQuery();

            oCn.Close();
        }
    }
}

Uma configuração que costuma ser omitida consiste no CommandType do objeto Command. Essa propriedade ajuda o objeto Command a processar o comando, informando antecipadamente a ele se o comando que vai ser executado é uma stored procedure, o nome de uma tabela ou um comando de texto free-form. Se você não definir essa propriedade, o objeto Command usará o padrão CommandType.Text; embora ainda funcione com stored procedures, ele será executado de maneira mais eficaz se você o avisar antes do tempo:

oCmd.CommandType = CommandType.StoredProcedure;

Também tento saber se o objeto Parameter poderia ser usado em uma consulta no lugar dos valores de parâmetro hardcoding de uma instrução SQL statement. Na Listagem 7, a instrução UPDATE é executada duas vezes, por isso faz sentido que o objeto Parameter seja usado para os valores de City e CustomerID. Se essa não fosse uma consulta parametrizada, a preparação do SQL não faria muito sentido, já que as instruções SQL seriam diferentes e a preparação e execução da instrução resultaria ineficaz. Além disso, ao utilizar uma consulta parametrizada, você elimina a necessidade de chateações, como apóstrofes incorporados. Observe na Listagem 5 que os valores não possuem apóstrofes incorporados em torno deles, nem o fazem os placeholders dos parâmetros na string SQL. Se precisasse substituir os placeholders dos parâmetros pelos valores propriamente ditos, eu teria de incluir os apóstrofes incorporados, desta maneira:

string sSQL = "UPDATE Customers SET City = 'Berlin2' 
WHERE CustomerID = 'ALFKI'";

O uso de instruções e stored procedures SQL parametrizados também é bom para a segurança, já que eles podem deter ataques de injeção SQL, o que ocorre quando um usuário mal-intencionado insere uma entrada que é usada para executar outras ações diferentes das originalmente pretendidas. Os objetos Parameter tratam os valores dos parâmetros como valores literais, e não como código executável. Assim, mesmo que o código SQL seja injetado, ele não será executado, mas sim usado como um valor de parâmetro.

O uso de valores incorporados para os parâmetros pode facilmente sair do controle à medida que o tamanho da instrução crescer e o número de parâmetros aumentar. Qualquer desenvolvedor que tenha executado as instruções SQL por meio da concatenação de string deve ter se frustrado em algum momento ao tentar colocar os apóstrofes incorporados nos lugares corretos. Imagine o código a seguir:

System.Text.StringBuilder oSb = new System.Text.StringBuilder();
oSb.Append("UPDATE Customers SET City = 'Berlin2', ");
oSb.Append("Address = 'Some address', ContactName = 'Some name' ");
oSb.Append("ContactTitle = 'Some title', CompanyName = 'Some company'");
oSb.Append("WHERE CustomerID = 'ALFKI'");

O StringBuilder pode tornar a operação mais eficaz em alguns cenários, mas você ainda precisa ter cuidado ao incorporar os apóstrofes nos lugares corretos. No caso de consultas parametrizadas, esse problema não existe.

Isto me leva ao tópico sobre a concatenação de fragmentos de código SQL em uma string. Esse processo é tratado de maneira muito mais eficaz pelo objeto StringBuilder do que pela concatenação conjunta de fragmentos de string, já que o objeto de string é imutável. É recomendável utilizar o StringBuilder sempre que se estiver concatenando mais de duas strings. Dito isso, é necessário reiterar que você só deve criar strings SQL como essas quando absolutamente necessário. Se forem criadas incorretamente, você pode ficar exposto a uma ampla variedade de ataques, e a Microsoft desencoraja esta prática sempre que possível.

Início da páginaInício da página

Transações do ADO.NET

As transações de dados são parte importante da maioria dos aplicativos empresariais. No entanto, elas constituem uma área que poderá facilmente se tornar ineficaz quando não gerenciada. A exemplo das conexões, as transações devem ser o mais curtas possível. Ou seja, você deve iniciá-las o mais tarde que puder e efetuar o commit (efetivar) ou o roll back (desfazer) o quanto antes. Entre o momento em que a transação é iniciada e a hora em que ela termina, somente deverão ser executadas as instruções SQL cruciais, como INSERT, UPDATE e DELETE. O código de modificação não-SQL deve ser evitado dentro da transação, já que esta mantém os bloqueios abertos e reduz a concorrência.

Por exemplo, a criação de transações explícitas deve ser evitada quando houver uma única instrução UPDATE em execução. Nesse caso, já existe uma transação implícita em torno da instrução UPDATE e, por isso, a criação de uma transação explícita adicionará uma sobrecarga desnecessária. Contudo, caso seja executado um segundo comando que precise ser incluído na transação atômica junto com o primeiro comando UPDATE, recomenda-se usar uma transação. Observe o código da Listagem 8 para ver como atribuir vários comandos a uma única transação.

Listagem 8 Vários Comandos em uma Transação

SqlTransaction oTran = null;
try {
    oCn.Open();
    oTran = oCn.BeginTransaction();

    oCmd1.Transaction = oTran;
    oCmd1.ExecuteNonQuery();

    oCmd2.Transaction = oTran;
    oCmd2.ExecuteNonQuery();

    oTran.Commit();
}
catch {
    oTran.Rollback();
    throw;
}
finally {
    oCn.Close();
}

Os objetos Transaction só estarão disponíveis se o provedor de dados os expuserem. Esses objetos devem ser criados explicitamente e, em seguida, atribuídos aos objetos Command apropriados. O objeto Transaction poderá então ser efetivado (commit) ou desfeito (roll back), conforme mostrado na Listagem 8. Além disso, certifique-se de liberar seus os recursos nos blocos finally. A listagem mostra como um objeto Connection é fechado, independentemente de a transação ter êxito ou falhar.

Início da páginaInício da página

Conclusão

Sempre ouvimos dizer que são as pequenas coisas que importam. É claro, essa máxima não se destinava à criação de aplicativos de software empresariais, mas ela certamente se aplica a esse caso. Analisados isoladamente, esses pequenos exemplos de ineficiências dificilmente fazem uma diferença perceptível no aplicativo do computador de um desenvolvedor. No entanto, lembre-se que, quando você coloca dezenas, centenas ou milhares de usuários utilizando o aplicativo ao mesmo tempo, esses pequenos problemas podem realmente retardar o programa e prejudicar seus usuários. Costumo criar uma lista de verificação dos itens referentes à arquitetura do aplicativo e a utilizo como base para a revisão do código. Além dos recursos que mencionei aqui, o DataSet, os recursos XML e o SQL em lote são, todos eles, áreas que podem se beneficiar de um pequeno ajuste de desempenho para melhorar a eficácia do acesso aos dados.

John Papa (mmdata@microsoft.com) é um fanático em baseball que passa a maioria de suas noites de verão torcendo pelos Yankees com suas duas filhinhas, sua esposa e seu cão fiel, Kadi. É autor de diversos livros sobre ADO, XML e SQL Server e costuma dar palestras em conferências do setor, como a VSLive.


Início da páginaInício da página