msdn10_capa.JPG

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

 

Migrando do ADO para o ADO.NET

por John Papa

 

Recentemente, a tecnologia ADO tornou-se o método preferido para a implementação de acesso a dados em aplicativos Windows®. Atualmente, são usados inúmeros aplicativos ADO, e muitos desenvolvedores estão bastante familiarizados com o desenvolvimento ADO. Com o lançamento do Microsoft® .NET Framework, surgiu o ADO.NET—a evolução do ADO. Embora haja semelhanças entre ADO e ADO.NET, seu funcionamento e sua base são bem diferentes. Para ajudá-lo a realizar uma transição sem problemas para o ADO.NET, analisarei como algumas tarefas comuns são executadas no ADO.NET.

Incluirei várias situações de acesso a dados, mostrarei como abordá-las com ADO e mostrarei como são executadas com ADO.NET no ASP.NET usando-se C#. Para começar, examinarei as semelhanças em uma conexão com uma fonte de dados. Em seguida, examinarei como o objeto ADO Recordset evoluiu para diversos objetos e métodos distintos e centrados no ADO.NET. Finalmente, investigarei cursores firehose, retornando um valor único de um rowset e trabalhando com XML.

 

A evolução do ADO

Alguns recursos do ADO tradicional, como o estabelecimento de conexão com uma fonte de dados, mudaram pouco na nova versão. Outros recursos evoluíram consideravelmente, como a capacidade de representar um rowset desconectado, persisti-lo em XML e transformá-lo em um rowset hierárquico. Um motivo para as significativas alterações é que recursos de XML e de data-shaping (formatação de dados) foram inclusões posteriores ao ADO; no ADO.NET, eles foram incorporados ao design logo no início.

O ADO tradicional é bastante leve quando comparado às antigas ferramentas de acesso a dados, como DAO e RDO. Um recurso que tornou o ADO tão popular no desenvolvimento em n camadas com o Visual Basic® 6.0 é seu modelo de objetos simples e de fácil navegação. Os objetos ADO Connection e Command convertem-se em objetos ADO.NET Connection e Command de forma relativamente direta, mas os recursos do objeto ADO Recordset convertem-se em vários objetos e métodos diferentes no ADO.NET.

O ADO continua uma ferramenta de acesso a dados poderosa e popular, em parte devido a seus recursos XML e à sua capacidade de gerenciar rowsets desconectados. Um ADO Recordset pode ser desconectado de sua fonte de dados e do objeto Connection definindo-se sua propriedade CursorLocation como adUseClient, seu CursorType como adOpenStatic e seu LockType como adLockBatchOptimistic. Em seguida, quando o recordset tiver sido aberto e carregado, ele poderá ser desconectado definindo-se a propriedade ActiveConnection do recordset como Nothing:

 

'---Disconnecting an ADO Recordset

oRs.CursorLocation = adUseClient

oRs.CursorType = adOpenStatic

oRs.LockType = adLockBatchOptimistic '— Or use adLockReadOnly

oRS.Open

Set oRS.ActiveConnection = Nothing

 

Recursos XML não foram integrados ao ADO desde o início, mas à medida que o XML tornou-se mais comum, seus recursos foram incorporados nas versões posteriores do ADO. O método Save do objeto ADO Recordset pode usar as fileiras e colunas do recordset e convertê-las em um esquema XML predefinido e, em seguida, persisti-los em um arquivo ou stream. O esquema XML não é muito flexível, mas foi a primeira tentativa real de persistência de XML de um rowset ADO e ofereceu aos desenvolvedores uma idéia dos rumos do desenvolvimento. O recordset também pode ser carregado de um arquivo XML, desde que use o mesmo esquema XML esperado. O código abaixo mostra como o ADO pode salvar um recordset em um arquivo XML:

 

.Save "c:\MyRowSet.xml", adPersistXML

 

O rowset pode ser salvo em um stream como a saída do objeto Response de uma página ASP. Por exemplo, um código ASP pode responder a uma solicitação, recuperar um rowset e enviá-lo ao objeto Response, que o retornaria ao cliente. O código abaixo demonstra como salvar o conteúdo de um recordset em um objeto Stream. Como alternativa, o primeiro argumento do método Save pode ser substituído pelo objeto Response, para que envie o rowset XML ao navegador de origem:

 

Dim oStm As ADODB.Stream

Set oStm = New ADODB.Stream

oRs.Save oStm, adPersistXML

 

O ADO e o ASP tradicionais têm a capacidade de enviar streams. É claro que, com o .NET isso pode ser feito por Web services, que oferecem mais recursos do que qualquer solução que possa ser facilmente criada com ADO e ASP tradicionais. Na verdade, o Web services é uma tecnologia destinada a processar solicitações de dados e a transmitir os dados em XML por HTTP. Embora o ADO possa enviar XML por stream, isso não foi inicialmente considerado em seu design. O objeto ADO.NET DataSet tem métodos WriteXml e WriteXmlSchema que permitem que seu conteúdo seja exportado para um arquivo ou stream XML. Assim, embora o método Save do ADO Recordset tenha sido aperfeiçoado para poder exportar seu conteúdo para XML, o ADO.NET DataSet sempre teve essa funcionalidade.

O ADO.NET além de persistir um DataSet em XML e carregar outro de XML, também pode usar sua estrutura semelhante a XML de outras maneiras em proveito próprio. Por exemplo, como o DataSet é fundamentalmente representado como XML, ele pode ser passado facilmente entre camadas físicas e lógicas. Isso significa que XML pode ser passado através de redes protegidas via HTTP, pois tem a forma de XML baseado em texto.

Como sua base é XML, o ADO.NET pode funcionar desconectado. Embora um ADO Recordset tradicional possa estar conectado ou desconectado, dependendo de várias configurações de propriedades (como CursorType = adOpenStatic e CursorLocation = adUseClient), o ADO.NET tem objetos RowSet dedicados que estão conectados a uma fonte de dados (o DataReader) ou desconectados (o DataSet).

 

Conexões

Criar conexões com o ADO tradicional e com o ADO.NET são processos bastante semelhantes. Basicamente, você declara o objeto de conexão, cria uma instância dele, define sua string de conexão e o abre. Dê uma olhada na Listagem 1. O primeiro exemplo mostra como abrir uma conexão usando ASP e ADO, e o segundo exemplo mostra como abrir uma conexão usando ASP.NET e ADO.NET.

 

 

Listagem 1 Abertura de conexão

 

‘ASP e ADO tradicionais em VBScript

dim oCn

dim sCn

set oCn = Server.CreateObject("ADODB.Connection")

sCn = "Provider=SQLOLEDB; Data Source=(local);" & _

      "Initial Catalog=Northwind;Integrated Security=SSPI"

oCn.Open sCn

 

‘ASP.NET e ADO.NET em C#

String sCn = "Provider=SQLOLEDB; Data Source=(local); Initial" +    

             "Catalog=Northwind;Integrated Security=SSPI";

SqlConnection oCn = new SqlConnection(sCn);

oCn.Open();

 

Uma grande diferença na criação de conexões com ADO e ADO.NET é que o ADO ajusta todas as conexões a todos os tipos de origens de dados em um único objeto Connection. O ADO.NET pode ter objetos separados que representem conexões com diferentes origens de dados. Por exemplo, o ADO.NET tem um namespace chamado System.Data.SqlClient, que armazena todos os objetos ADO.NET específicos do SQL Server™, incluindo o objeto SqlConnection. O objeto SqlConnection foi projetado especificamente para comunicar-se com bancos de dados SQL Server e, assim, é a forma mais rápida e com mais recursos para interagir com o SQL Server. Existe também um namespace mais genérico chamado System.Data.OleDb, que pode se comunicar com qualquer fonte de dados compatível com OLE DB. No ADO.NET, você pode criar vários namespaces de provedor de dados para conectar-se a uma fonte de dados específica, tornando o acesso mais fácil e eficaz, e permitindo que cada namespace explore os recursos do provedor de dados de destino. Se um aplicativo precisar alterar o tipo de provedor de dados dinamicamente ou focar um provedor que não tenha um objeto de conexão ADO.NET específico, ele poderá usar OleDbConnection.

 

Recordset para leitores

O ADO Recordset, que se comporta de forma diferente dependendo de como suas propriedades estão definidas, foi dividido em vários objetos e métodos diferentes no ADO.NET. Dessa forma, os objetos do ADO.NET funcionam de forma mais eficaz, já que seus objetos podem se concentrar no que fazem melhor, em vez de tentarem fazer de tudo. O ADO Recordset pode ser um rowset conectado e um rowset desconectado. Pode funcionar como um cursor forward-only read-only (firehose) ou permitir movimentações para trás, para frente e no meio do rowset. Um ADO Recordset pode permitir alterar dados diretamente no banco de dados e salvar e enviar as alterações em lote para o banco de dados. A questão é que um ADO Recordset pode ter muitas funções. O ADO.NET divide essa funcionalidade em objetos que são otimizados para executar suas tarefas específicas. Assim, os recursos do ADO Recordset foram divididos no objeto DataSet, no objeto DataReader e em partes dos objetos DataAdapter e Command.

Cursores forward-only read-only exigem uma conexão permanente com uma fonte de dados. O ADO.NET tem um objeto DataReader que, quando aberto, sempre está conectado. Foi escrito especificamente para um provedor de dados, como SQL Server, Oracle e o provedor mais genérico OLE DB. Assim, o objeto SqlDataReader pode se conectar a um banco de dados SQL Server e funcionar como um cursor firehose fazendo loop em milhares de registros, se necessário. O SqlDataReader fornece acesso somente fast-forward a resultados de consulta. Recupera um registro dos resultados da consulta do banco de dados e deixa a conexão aberta, para poder obter o próximo registro em seqüência. O ADO.NET DataReader é extremamente eficaz, porque não tem suporte a todos os recursos do ADO Recordset. É uma máquina de cursor firehose enxuta. Os exemplos da Listagem 2 mostram como implementar um cursor forward-only em ADO clássico e em ADO.NET. Observe que, no ADO.NET, o método Read do objeto DataReader move a posição automaticamente para o próximo registro. Isso elimina o problema comum de loop infinito que os desenvolvedores enfrentam com o ADO tradicional na omissão do método MoveNext do ADO Recordset.

 

Listagem 2 Implementação de cursor forward-only

 

'ASP e ADO

oRs.ActiveConnection = oCn

oRs.LockType = adLockReadOnly

oRs.CursorType = adOpenForwardOnly

oRs.CursorLocation = adUseServer

oRs.Open sSQL

Do While Not oRs.EOF

    Response.Write oRs("CompanyName") & "
"

    oRs.MoveNext

Loop

 

'ASP.NET e ADO.NET em C#

SqlDataReader oDr = oCmd.ExecuteReader();

while(oDr.Read())

{

    Response.Write(oDr[0] + "
");

}

 

Outra diferença entre ADO e ADO.NET é como o cursor forward-only é preenchido. No ADO, todos os rowsets, forward-only ou não, estão contidos no objeto Recordset e são abertos via método Recordset.Open ou métodos Execute de Connection ou Command. No ADO.NET, existe um método específico criado para recuperar dados forward-only em um DataReader: o método ExecuteReader do objeto Command. Esse método informa ao objeto Command para configurar especificamente o objeto DataReader para que o provedor de dados processe os resultados de maneira forward-only otimizada, como mostrado no exemplo anterior. Também há um método do objeto Command chamado ExecuteXmlReader que informa ao objeto Command para configurar os resultados da consulta para que sejam processados por um objeto XmlReader. O objeto XmlReader pode ser usado para converter e processar resultados de consulta XML como mostrado no exemplo de ASP.NET a seguir:

 

oCn.Open();

SqlCommand oCmd = new SqlCommand("SELECT * FROM Orders FOR XML AUTO",oCn);

oCmd.CommandType = CommandType.Text;

XmlReader oXR = oCmd.ExecuteXmlReader();

while(oXR.Read())

{

    Response.Write(oXR.ReadOuterXml());

}

 

Esse exemplo mostra como o XML pode ser enviado para o navegador de origem. Entretanto, o XML também poderia ser coletado e enviado em stream para Response de forma inteira.

 

Comandos de valor único

Em algum momento, a maioria dos aplicativos precisa recuperar um valor único de uma consulta de banco de dados. A forma padrão de fazer isso no ADO clássico é configurar a instrução SQL e abrir um recordset que contenha os resultados da pesquisa. Como há somente uma fileira e uma coluna nos resultados da pesquisa, parece excessivo obter exatamente esse valor. O exemplo abaixo recupera um valor único de uma consulta. O exemplo captura o número de fileiras na tabela Orders:

 

'-- ASP and ADO               

Set oRs = Server.CreateObject("ADODB.Recordset")

oRs.ActiveConnection = oCn

oRs.Open "SELECT COUNT(*) As iRowCount FROM Orders"

iCount = oRs.Fields("iRowCount").Value

 

O ADO.NET apresenta uma nova forma de obter um valor único dos resultados de uma consulta quando você espera o retorno de apenas uma fileira e uma coluna. O comando ADO.NET tem um método ExecuteScalar que retorna o primeiro valor de fileira e coluna da consulta associada. Isso gera pouquíssimo overhead, pois não é preciso criar um rowset, localizar o valor e fechar o rowset. O método ExecuteScalar é otimizado para a situação específica em que a recuperação de um valor único é necessária. O exemplo abaixo obtém o mesmo resultado que o exemplo anterior, mas usando ASP.NET e ADO.NET com o método ExecuteScalar:

 

string sSql = "SELECT COUNT(*) As iRowCount FROM Orders";

SqlCommand oCmd = new SqlCommand(sSql, oCn);

oCmd.CommandType = CommandType.Text;

int iCount = (int)oCmd.ExecuteScalar();

 

Outra forma de recuperar um valor único é usar um parâmetro de saída de um stored procedure. Essa técnica também pode recuperar alguns valores de uma única linha, por exemplo. Está disponível em ADO e ADO.NET, embora o ADO.NET tenha ampliado os recursos de parâmetro de saída. Para capturar o valor de uma variável de saída no ADO.NET a partir de um objeto Command, execute a consulta usando o método ExecuteNonQuery. Esse método informa o ADO.NET que a consulta não retornará um rowset, e assim o overhead de um DataSet ou DataReader será evitado:

 

oCmd.ExecuteNonQuery();

oCmd.UpdatedRowSource = UpdateRowSource.OutputParameters;

int iOrderID = (int)oCmd.Parameters["@OrderID"].Value;

 

Este código define então a propriedade UpdatedRowSource para que indique os parâmetros de saída, pressumindo-se que eles tenham sido definidos. Em seguida, os valores de saída poderão ser recuperados. Isso foi obtido no ADO tradicional usando-se um parâmetro obscuro no método Execute do objeto Connection, mas é muito direto no ADO.NET, como você pode ver. É claro que o ADO.NET também tem um método otimizado para o retorno de um rowset padrão— o método Execute do objeto Command.

O objeto Recordset no ADO tradicional pode resolver as instruções UPDATE, INSERT e DELETE que são necessárias para que o Recordset seja capaz de aplicar ao banco de dados subjacente quaisquer alterações feitas no Recordset. Embora esse recurso seja útil, ele também acarreta muito overhead, pois precisa voltar ao banco de dados para descobrir como desempenhar essa função.

Os objetos ADO.NET também podem fazer isso com o objeto CommandBuilder; entretanto, isso também acarreta overhead. Na maioria das situações de desenvolvimento, as instruções SELECT, INSERT, UPDATE e DELETE exatas são conhecidas no tempo de design. No ADO clássico, não havia um modo fácil de associar as consultas de ação com um Recordset para tirar proveito delas. Entretanto, no ADO.NET, um DataAdapter tem quatro objetos Command separados associados a ele para representar cada consulta de ação e a instrução SELECT. Isso permite ao DataAdapter ajudar a preencher um DataSet com os resultados de uma consulta e informa ao DataAdapter quais consultas de ação emitir para o banco de dados antecipadamente. Embora isso exija um pouco mais de codificação no tempo de design, o código extra é uma boa compensação pelos ganhos de desempenho (sem falar na facilidade de manutenção, pois o código é auto-explicativo).

 

Conclusão

Analisei a evolução de alguns recursos fundamentais da evolução do ADO para o ADO.NET, incluindo cursores firehose e XML persistente.