erun: yes">

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

Boa Idéia

Camada de abstração de acesso a dados

Utilizando Generics e Reflection

 

O .NET Framework 2.0 trouxe diversas melhorias em relação à versão anterior, uma das novas funcionalidades mais comentadas com certeza é o Generics, que permite o uso de parâmetros para tipos em classes e métodos. Com o uso de Generics, quando um objeto é instanciado, é possível transmitir à classe um tipo que será utilizado por ela.

Essa funcionalidade é muito poderosa, porém na maioria dos exemplos que a descrevem, vemos apenas o uso do namespace Collections.Generic, o que é apenas uma parcela do que pode ser feito com essa funcionalidade.

Outra melhoria bastante poderosa do .NET Framework é o namespace System.Reflection, que permite buscar informações presentes nos assemblies e metadados em tempo de execução. Com esse artigo, pretendo ir além do uso trivial de Generics, mostrando uma aplicação prática e de quebra, também mostrar um pouco do uso de Reflection.

 

Objetivo do projeto

Quando decidi escrever um artigo sobre Generics, a primeira idéia que veio na minha cabeça foi o termo “reutilização de código”, queria mostrar uma forma de criar uma classe reutilizável e que pudesse ser chamada por qualquer tipo de objeto.

Comecei a pensar nas tarefas repetitivas nos projetos de informática. Uma das tarefas mais repetitivas que temos em termos de programação é a manipulação de dados, afinal na maioria esmagadora de projetos, teremos um banco de dados guardando informações presentes no sistema.

O mais comum atualmente é ter um projeto que já implementa métodos para chamadas ao banco de dados e toda parte de manipulação é realizada via Stored Procedures, a própria Microsoft disponibiliza o famoso SQLHelper presente no Application Blocks para isso (msdn2.microsoft.com/en-us/library/ms954827.aspx).

Outra forma de minimizar o trabalho de acesso a dados é utilizar ferramentas como o NHibernate (www.hibernate.org/343.html), que permite que você se preocupe somente em criar as classes e ele faz toda parte de integração com o banco de dados. Optei por fazer algo “parecido” com o NHibernate.

Lembrei de um projeto que fiz por volta de 2001, quando ainda trabalhava com o nosso tão adorado Visual Basic 6 e ADO. Na época, criei um ActiveX que já fazia automaticamente inserts, updates etc., sempre baseados em informações presentes nos formulários. É claro que as limitações da ferramenta me obrigavam a guardar informações sobre o tipo de dado presente no campo em propriedades como a Tag e até no próprio nome do componente. Mas até que funcionava bem!

Foi pensando nesse projeto que escrevi esse artigo, vamos criar um componente de manipulação de dados que faça todo trabalho “sujo” sozinho. Dessa forma o desenvolvedor não precisará se preocupar em criar SQL, ou Stored Procedures no banco de dados, pois ao instanciar o componente, todos os métodos de manipulação de dados já estarão implementados e prontos para serem utilizados.

Os métodos que decidi implementar foram os seguintes: Insert, Update, Delete, buscar um registro a partir do código e buscar uma lista de registros a partir de um filtro.

 

Importante: O foco desse artigo é didático, já existem diversas ferramentas, tanto comerciais quanto gratuitas, que possuem as mesmas funcionalidades que usaremos nesse projeto. É possível que com algumas melhorias ele possa ser utilizado para fins comerciais, mas não vou me preocupar com detalhes como manipulação de exceções, caracteres especiais, SQL Injection, suporte para diferentes tipos de bancos de dados etc. O projeto desenvolvido aqui é puramente educacional.

 

Esse projeto foi escrito para funcionar apenas com SQL Server e foi testado apenas no SQL Server 2005.

 

ADO.NET

Para deixar o código fácil de ser manipulado e o menos confuso possível, decidi separar a parte de ADO.NET em uma outra classe. Para os métodos que implementaremos, precisamos de uma classe auxiliar que realize as seguintes funções utilizando ADO.NET:

·         Executar uma inserção e buscar o código do registro inserido;

·         Executar uma query e buscar os dados retornados;

·         Executar uma query sem retorno de valores.

Não vou me ater a descrever essas funções agora, pois elas não são o foco principal desse projeto, mas estão descritas nos tópicos a seguir.

 

Obtendo detalhes relacionados ao banco a partir das classes

Como comentei anteriormente, o objetivo deste artigo é criar um componente que permita que o desenvolvedor possa escrever apenas o código das classes no projeto e não se preocupar com rotinas de manipulação de dados.

Para que isso aconteça o componente deve estar apto a gerar comandos SQL dinamicamente, e para isso, ele precisa ter acesso ao nome de tabelas, campos, tipo de campos etc. Caso essas informações estejam disponíveis no projeto, de uma forma que saibamos ler, temos condições de buscá-las utilizando Reflection.

Portanto devemos definir algumas regras que devem ser seguidas pelo desenvolvedor ao criar o projeto.

1.      Cada tabela deve ter uma classe correspondente no projeto;

2.      O nome da classe deve ser idêntico ao nome da tabela no banco de dados;

3.      Cada instância da classe representará um único registro da tabela;

4.      Cada campo da tabela deve ter uma propriedade correspondente na classe.

Para permitir que além de propriedades relacionadas com a tabela, outras propriedades possam estar presentes na classe, optei por utilizar um atributo para identificar as propriedades que sejam diretamente relacionadas com campos do banco de dados.

Outra funcionalidade muito importante é identificar a chave primária da tabela, assim poderia realizar updates e deletes com facilidade. Também optei por utilizar um atributo para identificar as propriedades que se referem à chave primária da tabela.

O atributo para identificação de propriedades relacionadas com campos, chamei de DataBaseField e o atributo para identificação da propriedade relacionada com a chave primária, chamei de DataBaseIDField.

 

Criando o projeto em si

Abra o Visual Studio 2005 e crie uma nova solução vazia (Blank Solution) chamada de “DBAbstraction”. Adicione um novo projeto do tipo Class Library chamado de “GenericDBClass” e exclua a classe que é criada por padrão no projeto.

Podemos começar a trabalhar no que será necessário para fazer a integração com o banco de dados. Como a identificação das propriedades relacionadas com campos da tabela será feita a partir de atributos customizados, a primeira programação a fazer é criar essas classes. Adicione uma nova classe no projeto chamada “DataBaseField.cs” e adicione o código descrito na Listagem 1.

 

Listagem 1. Classe DataBaseField

using System;

using System.Collections.Generic;

using System.Text;

 

namespace GenericDB

{

    public class DataBaseField : Attribute { }

}

 

Como pode ser visto, essa classe precisa apenas herdar de Attribute, assim ela pode ser utilizada como atributo. Agora vamos criar o atributo para identificar a chave primária. Adicione uma nova classe no projeto chamada “DataBaseIDField.cs” e adicione o código descrito na Listagem 2. Como a chave primária também representa um campo, essa classe herda de DataBaseField.

 

Listagem 2. Classe DataBaseIDField

using System;

using System.Collections.Generic;

using System.Text;

 

namespace GenericDB

{

   public class DataBaseIDField : DataBaseField { }

}

 

Armazenando detalhes dos campos

A base do projeto é a geração dinâmica de comandos SQL portanto, precisava ter uma forma de guardar detalhes dos campos da tabela sendo manipulada. Para isso, crie uma classe chamada “DatabaseFieldDetail.cs” para armazenar as informações dos campos, como podemos ver na Listagem 3.

 

Listagem 3. Classe para armazenar informações dos campos

using System;

using System.Collections.Generic;

using System.Text;

using System.Reflection;

 

namespace GenericDB

{

  public class DatabaseFieldDetail

  {

    //nome do campo

    public string Name;

    //propriedades do campo

    public PropertyInfo PropertyDetails;

    //delimitador usado na query SQL

    public string Delimiter;

    //indica se o campo é o ID da tabela ou não

    public bool IsIdentity;

    public DatabaseFieldDetail() { }

    public DatabaseFieldDetail(string name,

      PropertyInfo propertydetails, string delimiter,

      bool isidentity)

    {

      //define os valores das propriedades

      Name = name;

      PropertyDetails = propertydetails;

      Delimiter = delimiter;

      IsIdentity = isidentity;

    }

  }

}

 

As informações armazenadas dentro da classe são Name, que contém o nome do campo, PropertyDetails, que contém as informações sobre a propriedade relacionada com o campo, Delimiter, que contém o caractere delimitador do tipo de dado do campo no SQL e IsIdentity, que identifica se o campo é a chave primária da tabela ou não.

 

Um pouco sobre Reflection

Para poder buscar informações sobre os atributos das propriedades, informações sobre os tipos de variáveis, configurar variáveis em runtime sem saber os nomes das mesmas etc., utilizamos o namespace System.Reflection.

Não vou entrar em detalhes, visto que Reflection realmente é algo que dá muito pano pra manga. Apenas vou destacar os métodos que utilizei dentro desse projeto. Caso você tenha interesse em aprofundar o assunto, aconselho a dar uma lida em: msdn2.microsoft.com/en-us/library/ms173183(VS.80).aspx.

O objeto utilizado para buscar/manipular informações sobre o tipo de classe passada como parâmetro, foi o System.Type. A partir do System.Type temos como buscar todos os detalhes de um objeto. Utilizando o GetProperties da classe System.Type obtemos um array de objetos do tipo System.Reflection.PropertyInfo. Esse objeto dá acesso a todos atributos e metadados de uma propriedade.

Utilizando o GetCustomAttributes da mesma classe, temos como buscar os atributos customizados de uma propriedade. Utilizando o GetValue temos como buscar o valor de uma propriedade de um objeto dado. Por fim, utilizando o SetValue temos como definir o valor de uma propriedade de um objeto.

 

Classe de acesso ao banco

Nosso próximo passo é criar a classe que faz o acesso ao banco de dados em si. Crie uma nova classe chamada “DBHelper.cs” e adicione o código descrito na Listagem 4, note que o código está comentado para um fácil entendimento do mesmo.

 

Listagem 4. Classe DBHelper

using System;

using System.Collections.Generic;

using System.Text;

using System.Data;

using System.Data.SqlClient;

using System.Configuration;

 

namespace GenericDB

{

  public class DBHelper

  {

    private SqlConnection connection;

    private string ConnectionString

    {

      get

      {

        return ConfigurationSettings.AppSettings[

          "GenericDataBaseConnectionString"].

          ToString();

      }

    }

    private SqlConnection Conexao

    {

      get

      {

        // conexão só é instanciada na primeira vez

        //que a propriedade é chamada

        if (connection == null)

        {

...

Quer ler esse conteúdo completo? Tenha acesso completo