Nova Classe de Conexão para Vários Bancos de Dados – Visão Geral
Leonel Fraga de Oliveira 17/02/2009 23:00

Olá! Agora que a nova Classe de Conexão já está em uso em sistemas de produção, bombando e não dando nenhum tipo de problema, já chegou a hora de mostrá-la para você.

Quando eu mencionei que tinha uma classe do gênero em um dos tópicos do fórum do Meio Bit, fui questionado sobre o NHibernate e outros frameworks de persistência de dados.

Estaria eu reinventando a roda não usando algo pronto?

De certa forma, talvez. Se eu for levar em consideração o meu ego como programador, é simplesmente lindo você ver uma cria sua em funcionamento e ser adotada pela empresa em que você trabalha. Vários dos sistemas que são desenvolvidos lá em ASP.NET usam esta classe com os mais variados bancos de dados: Firebird, SQL Server e Oracle.

Vendo de outra forma, esta é a forma que eu encontrei para aprimorar meus conhecimentos na plataforma .NET e na linguagem C#, pois esta versão da classe usa um conceito que eu não havia utilizado antes, o Reflection.

Usando mais um ponto de vista na brincadeira, ao utilizar um framework de terceiros estarei perdendo um pouco o controle das rotinas do sistema. E se um bug desse framework me ferra uma parte do sistema? Não temos tempo de ficar olhando o código dele (isso se for de código aberto), pois o cliente quer o sistema para ONTEM. Já que a “Classe de Conexão” já está bem consolidada e FUNCIONA, nada melhor que utilizá-la!

Após o break, irei descrever as modificações de código, os campos, propriedades e métodos que foram acrescentadas desde a última versão da Classe de Conexão. Para saber o que já está implementado antes da alteração, leiam os artigos da categoria “Conexão com Banco de Dados”.

Na parte de otimização de códigos, passei a utilizar os Data Provider Factories, que são classes genéricas dos objetos ADO.NET, ao invés de instanciar a classe específica do provider através das diretivas de compilação.

Estas diretivas agora são utilizadas apenas no construtor da classe Conexao para alimentar o novo campo protegido _DataProvider:

public Conexao()
{
#if FIREBIRD
    TTipoBancoDados TipoBD = TTipoBancoDados.tbFireBird;
    _DataProvider = "FirebirdSql.Data.FirebirdClient";
#endif
#if ORACLE
    TTipoBancoDados TipoBD = TTipoBancoDados.tbOracle;
    _DataProvider = "System.Data.OracleClient";
#endif
#if SQLSERVER
    TTipoBancoDados TipoBD = TTipoBancoDados.tbSQLServer;
    _DataProvider = "System.Data.SqlClient";
#endif
#if ODBC
    TTipoBancoDados TipoBD = TTipoBancoDados.tbODBC;
    _DataProvider = "System.Data.Odbc";
#endif
#if OLEDB
    TTipoBancoDados TipoBD = TTipoBancoDados.tbOleDb;
    _DataProvider = "System.Data.OleDb";
#endif
}

Veja como ficou o método AddSQLParam:

Antigo:

protected void AddSQLParam(string pNomeParam, object pValor, ParameterDirection pDirecao)
{
    switch (TipoBD)
    {
        case TTipoBancoDados.tbFireBird:
            {
                #if FIREBIRD
                this.SQLParams.Add(new FbParameter(pNomeParam, pValor));
                #endif                        
                break;
            }
        case TTipoBancoDados.tbOracle:
            {
               #if ORACLE
                this.SQLParams.Add(new OracleParameter(pNomeParam, pValor));
                #endif                        
                break;
            }
        case TTipoBancoDados.tbSQLServer:
            {
                #if SQLSERVER                        
                this.SQLParams.Add(new SqlParameter(pNomeParam, pValor));
                #endif                        
                break;
            }
        case TTipoBancoDados.tbOleDb:
            {
                #if OLEDB                        
                this.SQLParams.Add(new OleDbParameter(pNomeParam, pValor));
                #endif                        
                break;
            }
        case TTipoBancoDados.tbODBC:
            {
                #if ODBC                        
                this.SQLParams.Add(new OdbcParameter(pNomeParam, pValor));
                #endif                        
                break;
            }
    }
    (this.SQLParams[SQLParams.Count - 1] as IDataParameter).Direction = pDirecao;
}

Novo:

protected void AddSQLParam(string pNomeParam, object pValor, ParameterDirection pDirecao)
{
    DbProviderFactory factory = DbProviderFactories.GetFactory(_DataProvider);
    this.SQLParams.Add((IDataParameter)factory.CreateParameter());
    (this.SQLParams[SQLParams.Count - 1] as IDataParameter).ParameterName = pNomeParam;
    (this.SQLParams[SQLParams.Count - 1] as IDataParameter).Value = pValor;
    (this.SQLParams[SQLParams.Count - 1] as IDataParameter).Direction = pDirecao;
}

Com isso, reduzimos o código deste método de 42 para apenas 8 linhas.

Um detalhe importante e que não posso deixar passar batido, é que a nova Classe de Conexão é 100% compatível com a versão anterior, podendo substituir a anterior pela nova sem problemas.

O modo de compilação e alimentação da string de conexão continuam igual: colocar nas propriedades do projeto que utiliza a Classe de Conexão a diretiva referente ao(s) banco de dados utilizado e a string de conexão em uma chave chamada strConexao na seção appSettings do arquivo de configuração da aplicação (web.config, app.config).

Campo _ExcludeFieldsAutoIsql :

Este Arraylist é utilizado pelo novo método AutoIsql, e serve para excluir determinados campos de uma tabela da geração automática de instruções SQL de Insert e Update.

Os nomes dos campos que estiverem nesse ArrayList não serão incluidos no SQL.

Campo _TableName :

Serve para indicar o nome da tabela que uma classe derivada (lembra, para utilizarmos a Classe de Conexão precisamos criar uma classe filha?) irá representar no C# (a classe que faz o mapeamento BD –> Objeto, que representa uma tabela do BD).

Campo _DataKeys :

Um array de string, serve para guardar as chaves primárias da tabela informada no campo _TableName.

Campo _FormatProperties :

Utilizado pelo novo método BindToUI, este ArrayList armazena strings de formatação de uma propriedade que será aplicada em um textbox correspondente na interface de usuário (por enquanto, somente interface Web).

Deve ser informado em cada item do ArrayList o nome da propriedade (variável pública, com métodos get e set) da classe e a string de formatação, separados por ponto-e-vírgula. Exemplo:

“DataNascimento;{0:dd/MM/yyyy}”

Campo _ParameterDefinedBy :

Esta string indica o caractere definidor de parâmetro de instruções SQL parametrizadas. Esta constante é utilizada pelo método AutoIsql e BindObject2Parameters. Por exemplo, se for trabalhar com SQL Server, seu valor é “@”, e se for Oracle, “:”.

Campo _NotConvention2DBNull:

Este ArrayList serve para que os campos da tabela informadas nele não sofram a conversão (seguindo uma convenção que eu estipulei para facilitar a manipulação dentro da UI e de atribuição no objeto) de Zero (campos numéricos), String vazia e DateTime.MinValue para Nulo. Por exemplo, se um campo numérico for informado como zero na propriedade, eu costumo colocar o estado nulo no banco de dados, principalmente se o campo em questão for uma chave estrangeira, visto que eu nunca utilizo o valor zero como um identificador único (ou seja, se eu informar zero em uma PK, vai violar esta chave, visto que nunca existirá um ID igual a 0).

Método AlterSQLParamDirection:

Altera a direção de um parâmetro que esteja na coleção SQLParams. Utilizo esta função caso eu queira alterar um parâmetro criado via BindObject2Parameters antes de chamar a execução do SQL que usa este parâmetro.

Método AlterSQLParamValue:

Análogo ao AlterSQLParamDirection, altera o valor de um parâmetro da coleção SQLParams.

Médodo Select:

Opa… Este método já existe!

Sim, mas ele teve a sua funcionalidade extendida: Além de adicionar os campos da tabela na coleção ListaCamposTabela (usado se fomos ler os campos através da propriedade indexada this), agora também atribui AUTOMATICAMENTE os valores diretamente nas variáveis privadas da classe filha. Esta é a primeira benesse da aplicação do Reflection na nossa Classe de Conexão :-)

Só que para isto funcionar corretamente, as variáveis privadas da classe e que tenham seu correspondente na tabela (ou na instrução SQL em _SelectSQL) do banco de dados deverão ter seu nome IDÊNTICO ao campo da tabela precedido de underline. Exemplo:

Campo NOME, tipo varchar –> Variável correspondente: string _NOME;

Esta é o primeiro cuidado que devemos tomar para que possamos economizar código lá na frente. Logo mais temos outras convenções a seguir.

Ah, e também devemos ter o cuidado de não nomear campos de tabela com o nome de variáveis privadas da classe Conexao!

Método AutoIsql:

Gera instruções SQL parametrizadas para as operações de Insert, Update e Delete para a tabela informada na variável _TableName.

Este método pega os campos DIRETAMENTE da tabela do banco de dados, não necessitando de informação prévia sobre eles (somente das chaves primárias, leia parágrafo abaixo).

Nas instruções de Update e Delete, a cláusula Where das mesmas é montada através da informação fornecida na variável _DataKeys.

Ele monta instruções SQL parametrizadas, ou seja, os valores serão informados através de parâmetros. Estes valores serão atribuídos através do método BindObject2Parameters.

Método BindObject2Parameters :

Cria, a partir da tabela informada na propriedade _TableName, parâmetros para a atribuição de valores a partir das variáveis privadas correspondentes aos campos.

Funciona da seguinte forma: Lê o a tabela do BD, pega o primeiro campo, verifica nas variáveis privadas se há uma com o mesmo nome precedido de underline. Se existir, verifica se ele utiliza a “convenção de zero para nulo” e cria um item na coleção _SQLParams com este parâmetro já atribuído. E faz isso para cada campo da tabela.

Método ConverterDB2Obj:

Converte um tipo de Banco de Dados em um tipo do C#. Utilizado principalmente no caso do campo vir nulo da base de dados e a propriedade não for do tipo nulável.

Método BindToUI:

Uma das obras-prima dessa nova Classe de Conexão: Atribui as propriedades (com métodos get e set) da classe na interface de usuário.

Passamos como parâmetro a classe da página web (por enquanto, só trabalha com interfaces Web) que terá seus componentes atribuídos, as informações de seu tipo (método GetType() ) e um arraylist que contém os nomes dos componentes que não serão atribuídos caso haja correspondente na classe.

Para fazer a correspondência da propriedade da classe com o componente da interface de usuário, faremos a seguinte convenção na propriedade Name do componente:

Prefixo de 3 letras + Nome da Classe + Nome da Propriedade

Por exemplo, temos uma classe de nome Funcionario e queremos atribuir a propriedade Nome em um textbox. O nome do componente vai ficar assim: tbxFuncionarioNome.

Tá, poderão existir casos em que o ID de um componente vai ficar com um nome cavalar. Este é mais um preço que teremos que pagar pela comodidade de manutenção futura (imagina o trabalho que daria acrescentar um campo novo, procurar no SQL, fazer as atribuições na UI…).

Caso a propriedade seja indexada, informaremos, na convenção apresentada anteriormente, o índice da propriedade precedido de underline, depois do nome do controle, exemplo: Campo Telefone é um array com 5 posições e iremos atribuir a posição 0: tbxFuncionarioTelefone_0.

As seguintes propriedades serão atribuídas, dependendo do tipo de controle:

TextBox -> Text

DropDownList -> SelectedValue

RadioButton -> Checked (somente campos booleanos, inteiros: 0 -> false; > 0 -> true ou string: "S"/"SIM" -> true, "N"/"NÃO" -> false

Label -> Text

CheckBox -> Checked (somente campos booleanos ou numéricos: 0 -> false; > 0 -> true

CheckBoxList -> SelectedValue

RadioButtonList SelectedValue

Hidden Field (HTML Controls) –> Value

Embora tenha um método para pegar o prefixo dependendo do controle, ele não fará diferença, pois pegará a informação do tipo diretamente do controle, porém, o nome do componente deve ter um prefixo de 3 (três) e somente 3 letras.

Método BindFromUI:

Faz a operação inversa do método BindToUI: Atribui os valores dos controles da interface de usuário em seus correspondentes na classe. Vale as mesmas convenções.

Método ConverterObj2DB (Uso interno):

Converte um tipo de objeto C# em um compatível com banco de dados. Utilizado principalmente quando o objeto (variável) que iremos converter for nula.

Método UIControlPrefix (Uso interno):

Retorna um prefixo correspondente ao tipo de controle informado no parâmetro:

tbx -> TextBox

ddl -> DropDownList

rbt -> RadioButton

lbl -> Label

chk -> CheckBox

cbl -> CheckBoxList

rbl -> RadioButtonList

hdf -> Hidden Field (HTML Controls)

Método ContainFormatInfo (Uso interno):

Verifica se uma propriedade contém informações de formatação (checadas na coleção _FormatProperties).

Método HasIndexIdentifier (Uso interno):

Verifica se um nome de controle é associado a uma propriedade indexada (convenção dos metodos BindFrom (ou To) UI.

Métodos ExcelPlan2DataTable e DataTable2ExcelPlan:

Explicado com detalhes em artigos anteriores, converte DataTable em Planilha Excel e vice-versa.

Método CreateList:

Cria um objeto List<> a partir de uma instrução SQL.

Passamos como parâmetros o tipo de cada item da lista (método GetType() de sua classe), a classe do objeto List que queremos criar (método GetType() de um objeto List, a instrução SQL de origem, e se esta instrução SQL possui parâmetros ou não.

Para funcionar corretamente, devemos seguir as convenções do método Select() da classe: nome de variável privada da classe = nome do campo no Select precedido de underline.

Ufa! Estes são os novos itens colocados nessa nova Classe de Conexão. No próximo artigo, teremos um exemplo em ASP.NET de um cadastro simples utilizando os novos conceitos, e uma sequência de artigos explicando alguns dos métodos mais interessantes desta classe, para aprendermos um pouquinho de Reflection e como ele é utilizado neste contexto.

Caso vocês já queiram se aventurar, faça o download abaixo e divirta-se ;-)

Nova Classe de Conexão (19 KB).

Um abraço!

Leonel Fraga de Oliveira Leonel Fraga de Oliveira é formado em Processamento de Dados na Faculdade de Tecnologia de São Paulo (FATEC-SP - 2002) e anteriormente em Técnico em Eletrônica, pela ETE Professor Aprígio Gonzaga (lá em 1999).
Atualmente trabalha como Analista de Sistemas na Prefeitura Municipal de São Caetano do Sul - SP
Tem como hobbies DJing (também trabalha como DJ freelancer) e ciclismo, além da manutenção dos sites NeoMatrix Light e NeoMatrix Tech.
Gosta de música eletrônica, tecnologia, cinema (super fã de Jornada nas Estrelas), gastronomia e outras coisas mais.


Compartilhe nas redes sociais

   

Deixe seu comentário

comments powered by Disqus