IntroduçãoO ADO.NET oferece capacidades robustas para desenvolvimento de banco de dados, com diversos objetos internos que proporcionam diferentes abordagens para programação de banco de dados. Essa flexibilidade, porém, pode confundir muitos iniciantes: devo usar DataReader ou DataAdapter? Para ler apenas uma pequena parte dos dados, é necessário preencher completamente um DataSet? Por que o DataReader não possui um método de atualização de dados como o RecordSet? Quais as vantagens reais do DataSet?Neste artigo, faremos uma análise comparativa entre os padrões de programação de banco de dados do .NET PetShop e do Duwamish. Se você também tem essas dúvidas, ao final deste texto, estará apto a definir o modelo mais adequado para suas necessidades específicas.Sumário- Breve introdução ao .NET PetShop e Duwamish
- Estrutura das aplicações
- Análise do acesso a dados do Duwmaish
- Análise do acesso a dados do .NET PetShop
- Análise e conclusões
Breve introdução ao .NET PetShop e DuwamishProvavelmente você já ouviu falar na famosa "guerra das lojas de pets". Sim, um dos protagonistas deste artigo é o .NET PetShop, o vencedor dessa disputa, que segundo a Microsoft, supera em 27 vezes a velocidade e possui 1/4 do código da loja PetStore baseada em J2EE. Embora a SUN tenha questionado a validade dessa comparação, o .NET PetShop certamente representa um tutorial clássico da plataforma .NET, oferecendo um "atalho" para superar soluções J2EE. Você pode baixá-lo em: http://www.gotdotnet.com/team/comparePágina inicial da loja de pets .NET PetShopO Duwamish, por outro lado, é uma aplicação .NET completa de uma livraria online, aparentemente simples porém internamente complexa. Como um exemplo oficial da Microsoft, ele está disponível tanto em C# quanto em VB.NET, acompanhado de extensa documentação em chinês. Se você possui o Visual Studio .NET, ele já está no seu disco rígido, aguardando instalação no diretório Enterprise Samples do VS.NET, por exemplo: C:\Program Files\Microsoft Visual Studio .NET\Enterprise Samples\Duwamish 7.0 CS. Página inicial da livraria online DuwamishEstrutura das aplicaçõesAmbas as lojas utilizam arquitetura de aplicação em n camadas (sem dúvida, a escolha ideal para desenvolvimento .NET, mesmo para projetos simples). A diferença está no número de camadas: o PetShop adota a estrutura de três camadas mais comum (camada de aprseentação, camada intermediária e camada de dados). Já o Duwamish implementa uma arquitetura de quatro camadas, com projetos separados para camada de apresentação, fachada de negócios, regras de negócios e camada de dados. Os prós e contras dessas estruturas e os motivos por trás delas não serão discutidos aqui, pois o foco deste artigo é o padrão de programação de banco de dados.Aálise do acesso a dados do DuwamishVamos primeiro analisar a livraria Duwamish, que utiliza um padrão de armazenamento de dados com DataAdapter e DataSet. A particularidade é a subclassificação do DataSet como transportador de dados entre camadas, empregando DataSet customizado para essa finalidade. Abaixo temos um exemplo de DataSet customizado:```csharp public class LivroDados : DataSet { public LivroDados() { // // Cria as tabelas no dataset // ConstruirTabelas(); }
private void ConstruirTabelas()
{
//
// Cria a tabela de Livros
//
DataTable tabela = new DataTable(TABELA_LIVROS);
DataColumnCollection colunas = tabela.Columns;
colunas.Add(CAMPO_PKID, typeof(System.Int32));
colunas.Add(CAMPO_TIPO_ID, typeof(System.Int32));
colunas.Add(CAMPO_EDITORA_ID, typeof(System.Int32));
colunas.Add(CAMPO_ANO_PUBLICACAO, typeof(System.Int16));
colunas.Add(CAMPO_ISBN, typeof(System.String));
colunas.Add(CAMPO_ARQUIVO_IMAGEM, typeof(System.String));
colunas.Add(CAMPO_TITULO, typeof(System.String));
colunas.Add(CAMPO_DESCRICAO, typeof(System.String));
colunas.Add(CAMPO_PRECO_UNITARIO, typeof(System.Decimal));
colunas.Add(CAMPO_CUSTO_UNITARIO, typeof(System.Decimal));
colunas.Add(CAMPO_TIPO_ITEM, typeof(System.String));
colunas.Add(CAMPO_NOME_EDITORA, typeof(System.String));
this.Tables.Add(tabela);
}
} Observa-se que ele possui um método ConstruirTabelas, chamado no construtor, associando assim a tabela de Livros customizada ao DataSet. Isso elimina a necessidade de mapeamento de colunas posteriormente, uma abordagem bastante inteligente.Com a estrutura de dados definida, vamos analisar a implementação da camada de dados. No Duwamish, existem 5 classes na camada de dados: Livros, Categorias, Clientes e Pedidos, cada uma responsável pelo acesso a dados específicos. Abaixo temos um exemplo de uma dessas classes:csharp private SqlDataAdapter comandoDados;
public LivroDados ObterLivroPorId(int idLivro) { return PreencherLivroDados("ObterLivroPorId", "@IdLivro", idLivro.ToString()); }
private LivroDados PreencherLivroDados(String textoComando, String nomeParametro, String valorParametro) { if (comandoDados == null) { throw new System.ObjectDisposedException(GetType().FullName); }
LivroDados dados = new LivroDados();
SqlCommand comando = comandoDados.SelectCommand;
comando.CommandText = textoComando;
comando.CommandType = CommandType.StoredProcedure; // usa stored procedure para performance
SqlParameter parametro = new SqlParameter(nomeParametro, SqlDbType.NVarChar, 255);
parametro.Value = valorParametro;
comando.Parameters.Add(parametro);
comandoDados.Fill(dados);
return dados;
} Aqui está o código da camada de dados, onde podemos ver que o Duwamish utiliza o DataAdapter para preencher o DataSet customizado e retorná-lo. O que chamou a atenção foi a presença de métodos específicos como ObterLivroPorId na camada de acesso a dados. Embora exista um método abstrato PreencherLivroDados, com três camadas acima, o que a camada inferior realiza? A resposta é a validação de dados. As camadas superiores realizam principalmente rigorosas verificações de validade dos dados (além de alguma lógica de transação complexa, mas não em grande quantidade). Veja o exemplo:csharp public ClienteDados ObterClientePorEmail(String email, String senha) { // // Verifica pré-condições // ValidacaoAplicacao.VerificarCondicao(email != String.Empty, "Endereço de email é obrigatório", ValidacaoAplicacao.NumeroLinha); ValidacaoAplicacao.VerificarCondicao(senha != String.Empty, "Senha é obrigatória", ValidacaoAplicacao.NumeroLinha);
//
// Obtém o dataset do cliente
//
ClienteDados dataSet;
using (DadosAcesso.Clientes acessoDadosCliente = new DadosAcesso.Clientes())
{
dataSet = acessoDadosCliente.CarregarClientePorEmail(email);
}
//
// Verifica a senha do cliente
//
DataRowCollection linhas = dataSet.Tables[ClienteDados.TABELA_CLIENTES].Rows;
if ((linhas.Count == 1) && linhas[0][ClienteDados.CAMPO_SENHA].Equals(senha))
{
return dataSet;
}
else
{
return null;
}
} Neste método, a única linha realmente responsável pelo acesso a dados é:csharp dataSet = acessoDadosCliente.CarregarClientePorEmail(email); Todas as outras linhas realizam validações de legitimidade. Percebe-se a importância da robustez em um desenvolvimento corporativo verdadeiro.Análise do acesso a dados do .NET PetShopFinalizada a análise do Duwamish, vamos agora examinar o mecanismo de acesso a dados do PetShop.O PetShop possui apenas um projeto, e sua abordagem de camadas consiste em colocar a camada intermediária e a camada de dados como arquivos cs no diretório Components. A camada de dados é representada por uma única classe chamada Database, que encapsula todas as operações de baixo nível no banco de dados. Abaixo temos um trecho do código de exemplo:csharp public void ExecutarProcedimento(string nomeProc, out SqlDataReader leitorDados) { SqlCommand cmd = CriarComando(nomeProc, null); leitorDados = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection); } Observamos um método de acesso a dados completamente diferente do Duwamish. Todos os métodos de acesso a dados são abstraídos em um método ExecutarProcedimento. Quanto à retorno de dados, ele retorna um DataReader diretamente para o chamador, que será responsável por processá-lo. Lembre-se de qual era o transportador de dados entre camadas no Duwamish? Exato, o DataSet, preenchido pela camada de dados e retornado para a camada intermediária. Aqui, o transportador entre camadas é o DataReader, que mais parece um "referenciador" de dados, pois os dados ainda não foram lidos.Vamos ver como o DataReader é "processado":csharp public ResultadoProduto[] ObterLista(string catid, int paginaAtual, int tamanhoPagina, ref int numResultados) { numResultados = 0; int indice = 0; SqlDataReader leitor = ObterLista(catid); ResultadoProduto[] resultados = new ResultadoProduto[tamanhoPagina];
// agora percorre a lista e extrai itens da página especificada
int inicio = (int)((paginaAtual - 1) * tamanhoPagina);
if (inicio <= 0) inicio = 1;
// pula
for (int i = 0; i < inicio - 1; i++) {
if (leitor.Read()) numResultados++;
}
if (inicio > 1) leitor.Read();
// lê os dados de interesse
while (leitor.Read()) {
if (indice < tamanhoPagina) {
resultados[indice] = new ResultadoProduto();
resultados[indice].produtoId = leitor.GetString(0);
resultados[indice].nome = leitor.GetString(1);
indice++;
}
numResultados++;
}
leitor.Close();
// verifica se é necessário redimensionar o array
if (indice == tamanhoPagina)
return resultados;
else {
// não é uma página completa, redimensiona o array
ResultadoProduto[] resultados2 = new ResultadoProduto[indice];
Array.Copy(resultados, resultados2, indice);
return resultados2;
}
} Notou os parâmetros paginaAtual e tamanhoPagina? A paginação de dados é realizada aqui, retornando apenas a quantidade mínima de dados necessários, em vez de simplesmente vincular toda a DataTable a um DataGrid, como muitos programadores preguiçosos fazem, causando grande redundância de dados.Aqui, os dados são realmente lidos e preenchidos manualmente em um array de objetos customizados. Vejamos a definição desse array:csharp public class ResultadoProduto { private string m_produtoId; private string m_nome;
// propriedades do produto
public string produtoId {
get { return m_produtoId; }
set { m_produtoId = value; }
}
public string nome {
get { return m_nome; }
set { m_nome = value; }
}
}