.NET - Uma reflexão sobre o Acesso a dados - A abordagem ADO .NET - I


Durante a última década, temos desenvolvido aplicações utilizando linguagens como VB6, ASP, Delphi, VB .NET e C#, usando componentes externos ou objetos para acessar bancos de dados e manter os dados internamente.

Ambas as tarefas são semelhantes em cada linguagem usada, mas elas são especialmente semelhantes na representação dos dados internos, onde os dados são organizados em estruturas construídas sobre o conceito de linhas e colunas. O resultado é que as aplicações que realizam o gerenciamento dos dados o fazem da mesma forma que eles são organizados no banco de dados.

Por que diferentes fornecedores oferecem aos desenvolvedores o mesmo modelo de programação ?

A resposta é simples: os desenvolvedores estão acostumados à representação tabular, e não têm necessidade de aprender mais nada para serem produtivos.

Além disso, essas estruturas genéricas podem conter quaisquer dados, desde que eles possam ser representados em linhas e colunas e mesmo dados provenientes de arquivos XML, web services podem ser organizados desta forma.

Uma das consequências desta postura é que os vendedores desenvolveram um subconjunto de objetos que podem representar qualquer informação sem que seja preciso escrever muitas linhas de código. Esses objetos são chamados contêirnes ou contentores de dados.

Usando DataSets e DataReaders como Contêiners de dados

Se você acompanhou a plataforma .NET desde o seu início e desenvolveu aplicações para acesso a dados. Com certeza já utilizou datasets e datareaders.

Eles eram muito convenientes pois com poucas linhas de código tínhamos um objeto que poderíamos vincular a muitos controles de dados vinculados e que , no caso do datareader, oferecia um bom desempenho.

Por outro lado usando um data adapter em combinação com um dataset tínhamos uma representação completa do banco de dados em memória onde poderíamos realizar operações de leitura e atualização de dados.

Nunca tínhamos sido tão produtivos... (santa ingenuidade...)

Com a ajuda dos assistentes da plataforma .NET e da integração destes objetos tínhamos a sensação que poderíamos criar qualquer aplicação usando apenas o recurso arrastar-soltar e algumas linhas de código. (as aplicações RAD)

Você Concorda ???

Vamos a um exemplo prático.

Suponha que você tenha um banco de dados com duas tabelas : Pedidos e DetalhesPedido com a seguinte estrutura:

Obs: As tabelas foram extraídas do banco de dados Northwind.mdf

Suponha que você tenha que exibir todos os pedidos em uma aplicação.

Era relativamente simples bastava fazer o seguinte:

  1. Criar uma conexão com o banco de dados;
  2. Criar um Adapter;
  3. Executar uma consulta e preencher um data table;
  4. Vincular o data table a um controle de visualização como um ListView, DataGridView, etc.

O código seria simples:


using (SqlConnection conn = new SqlConnection(connString))
{
 using (SqlDataAdapter da = new SqlDataAdapter("Select * from order", conn))
 {
    DataTable dt = new DataTable();
    da.Fill(dt);
    ListView1.DataSource = dt;
    ListView1.DataBind();
 }
}
Using conn As New SqlConnection(connString)
   Using da As New SqlDataAdapter("Select * from order", conn)
     Dim dt As New DataTable() 
     da.Fill(dt)
     ListView1.DataSource = dt
     ListView1.DataBind()
   End Using
End Using
C# VB .NET

Mas seria esse modelo o ideal em termos de produtividade ?

Teria o desenvolvedor condições de criar aplicações robustas, de fácil manutenção e reutilização usando este modelo ?

Vamos fazer uma análise dessa abordagem...

A maneira como o acesso a dados é realizado é completamente inseguro e genérico. (é ou não é ?)

Por um lado, você tem uma grande flexibilidade, porque você pode facilmente escrever código genérico para implementar funções que não conhecem os nomes dos campos da tabela e dependem da configuração.

Por outro lado, você perde a segurança de tipos. Você identifica um campo especificando seu nome através de strings , se o nome não estiver correto, você obtém uma exceção somente em tempo de execução. (runtime)

Você perde controle não somente sobre os nomes dos campos, mas também sobre os tipos de dados. Os DataReaders e DataTables (que são os itens que contém os dados em um DataSet) retornam os valores das colunas como do tipo Object (o tipo de dados base da plataforma .NET), de forma que você precisa realizar uma conversão forçada, um cast, dos valores para o tipo correto (ou invocar o método ToString quando pertinente).

Os DataReaders e DataTables não permitem a recuperação de dados de forma transparente sem afetar a interface com o usuário.

E isso não é um bom sinal.

Significa que seu aplicativo esta fortemente acoplado com a estrutura do banco de dados e qualquer mudança na estrutura requer que seu código também seja alterado.

Dessa forma temos que o código da interface têm que ser adaptado para refletir as mudanças ocorridos no banco de dados e esta é a razão mais importante porque a utilização destes objetos esta sendo substituída ou desencorajada.

Mesmo se você tiver os mesmos dados na memória, a maneira como eles são retornados tem influência em como eles são representados internamente. E este problema não deveria ser tratado na interface do usuário e sim no código de acesso aos dados.

Para retornar um valor de uma coluna armazenada em um DataReader ou DataTable geralmente referenciamos a coluna usando uma constante string. Ex:

object endereco = orders.Rows[0]["Endereco"];
Dim endereco As Object = orders.Rows(0)("Endereco")
C# VB .NET

A variável endereco é do tipo System.Object, e por isso pode conter potencialmente qualquer tipo de dados. Você pode até saber que ela contém um valor string, mas para usá-lo como uma string, você tem que executar explicitamente um casting ou operação de conversão da seguinte forma:

string _endereco = (string)orders.Rows[0]["Endereco"];
string _endereco = orders.Rows[0]["Endereco"].ToString();
Dim _endereco As String = DirectCast(orders.Rows(0)("Endereco"), String)
Dim _endereco As String = orders.Rows(0)("Endereco").ToString()
C# VB .NET

Realizar uma conversão ou casting tem um custo em termos de desempenho e uso de memória porque converter de um tipo de valor para um tipo de referência e vice-versa faz com que ocorra o boxing e unboxing. Em alguns casos, a conversão pode requerer o uso da interface IConvertible, o que causa um casting interno.

Boxing e Unboxing é um conceito essencial no sistema de tipos do VB.NET (C# também). Mas o que vem a ser isto de boxing e unboxing ?

 

Nota: Podemos traduzir boxing como empacotar e unboxing como desempacotar mas creio que o melhor mesmo é nos referirmos aos termos originais box e unboxing pois será assim que serão referenciados na literatura e em artigos.
 

O processo de conversão explícita de um tipo valor para um tipo referência é conhecido em .NET como Boxing (empacotar). O processo contrário a Boxing é conhecido como Unboxing. Nesse caso, o compilador verifica se o tipo valor a receber o conteúdo do tipo referência é equivalente a este último.
 

Referência: http://www.macoratti.net/vbn_box1.htm

Os DataReaders têm uma vantagem sobre os DataTable. Eles oferecem métodos tipados para acessar campos sem precisar de conversões explícitas. Tais métodos aceitam um parâmetro inteiro que representa o índice da coluna na linha. Os DataReaders também tem um método que retorna o índice de uma coluna, dado o seu nome, mas seu uso tende a desordenar o código e está sujeita a erros de digitação.

O DataSet é provavelmente uma das estruturas mais complexas na biblioteca de classes. NET.; ele contém uma ou mais instâncias DataTable, e cada uma delas tem uma lista de objetos DataRow feitos de um conjunto de objetos  DataColumn. Um DataTable pode ter uma chave primária composta por uma ou mais colunas e pode declarar que determinadas colunas têm uma relação de chave estrangeira com colunas em outro DataTable.

As colunas suportam o versionamento, o que significa que se você alterar o valor, tanto o velho como o novo valor são armazenados na coluna para executar verificações de simultaneidade/concorrência. Para enviar atualizações para o banco de dados, você tem que usar uma classe DbDataAdapter (ou, mais precisamente, uma das suas classes derivadas), que é ainda outro objeto.

Embora esses recursos sejam muitas vezes inúteis e ignorados pelos desenvolvedores,o DataSet internamente cria uma coleção vazia desses objetos. Este poderia ser uma consumo insignificante de recursos para um aplicativo independente e simples, mas em um ambiente multiusuário com milhares de pedidos, como uma aplicação web, isso se torna inaceitável.

Dessa forma é inútil otimizar o desempenho do banco de dados, índices de ajustes, modificando SQL, acrescentando sugestões, e assim por diante, se você desperdiçar recursos criando estruturas que você não vai precisar usar.

Ao contrário, um DataReader é construído para diferentes cenários. Um DataTable obtém todos os dados lidos do banco de dados na memória, mas muitas vezes você não precisa de todos os dados em memória e poderia buscá-los registro por registro do banco de dados.

Outra situação recorrente é nas atualizações de dados; muitas vezes você precisa ler dados, mas não precisa atualizá-los; em tais casos, algumas características, como a versão de linha, são inúteis.

Um DataReader é a melhor escolha nessas situações, porque ele recupera dados em um caminho read-only(somente leitura) sendo mais rápido. Embora aumente o desempenho, o DataReader ainda pode sofrer de problemas de casting e conversão de DataSet, mas esta perda de tempo é menor que o ganho que você recebe pela sua utilização.

Todos esses problemas citados acima podem parecer intimidadores, mas muitas aplicações se beneficiam do uso de banco de dados como estruturas. No entanto, em projetos corporativos, onde a base de código é grande e você precisa de mais controle e flexibilidade, você pode aproveitar o poder da programação orientada a objetos e das classes para organizar seus dados e assim obter um melhor resultado.

É isso que vamos discutir no próximo artigo : a era do paradigma da orientação a objetos - POO.

Acompanhe a continuação do artigo em : .NET - Uma reflexão sobre o Acesso a dados - A era OOP - II

1Pedro 3:8 Finalmente, sede todos de um mesmo sentimento, compassivos, cheios de amor fraternal, misericordiosos, humildes,
1Pedro 3:9
não retribuindo mal por mal, ou injúria por injúria; antes, pelo contrário, bendizendo; porque para isso fostes chamados, para herdardes uma bênção.

Referências:


José Carlos Macoratti