O NeoMatrix Tech está de casa nova!

Você deverá ser redirecionado em 6 segundos. Se não, visite:
http://www.leonelfraga.com/neomatrixtech
e atualize seus favoritos.

Aviso IMPORTANTÍSSIMO!

Aviso aos navegantes:

O NeoMatrix Tech mudou de casa!!!

A partir de agora, acessem pelo novo endereço:

http://www.leonelfraga.com/neomatrixtech

Ué... mas é só o domínio mudou de lugar?

R: Na verdade, não é bem assim hehe. Este domínio que você acessa agora aponta para um blog hospedado no Blogger, enquanto no novo, aponta para um blog na plataforma Wordpress, hospedagem própria, muito mais rápida e com um layout mais agradável de ler ;)

Não vou fechar este domínio igual ao que eu fiz com o NM Light (que já está 100% na nova plataforma). Talvez beeeeeeem depois eu faça isso.

Todos os posts daqui se encontram lá, e novos posts serão colocados somente no novo endereço.
A única coisa que não consegui importar foram os comentários. Mas em breve vai ter um post contando sobre a epopéia que foi migrar o NeoMatrix Tech!

Somente vou fechar a área de comentários daqui. Caso queiram comentar, favor ver o post correspondente no "Novo NeoMatrix Tech" e comentem por lá. É bem melhor! (pena que os permalinks "amigáveis para SEO" não funcionam lá, dá erro 404 e não consigo fazer a configuração funcionar. E olha que eu já vi vários artigos falando desse assunto :( ).

Quem assina o feed, já está lendo o conteúdo do novo NeoMatrix Tech!

sábado, 12 de abril de 2008

Framework de Abstração para Bancos de Dados - Parte 2 - Descrição dos Tipos

Salve galera!
No artigo anterior eu disponibilizei um conjunto de classes que visa simplificar a codificação de biblioteca de classes que fazem consulta à bancos de dados,
tirando a necessidade de instanciar cada objeto referente ao provider de conexão e trabalhar na maior parte do tempo sobre DataTables e tipos comuns do .NET.
Agora, a pergunta: Como se usa esse bicho?

Enquanto eu preparo um programa exemplo, um cadastro bem simples de Clientes com contatos e endereços (com pouqúissimos dados, mesmo se tivesse bastante dados,
não mudaria a nossa forma de codificar ;-) ) vou descrever cada componente da classe Conexao porém não vou colocar a assinatura dos métodos, salvo caso eles
tenham mais de uma sobrecarga. Vocês podem baixar o arquivo do post anterior para ver!


Classe TCampoCadastro:


É uma classe que associa cada campo de uma tabela a um valor. De grosso modo, representa cada campo de uma tabela (ou view, o que for) do banco de dados.
Conforme veremos, ela não "amarra o burro" com nenhum tipo específico em especial, vejamos as suas propriedades e métodos:

Propriedade NomeCampo -> Tipo string, representa o nome do campo da tabela no banco de dados
Propriedade Valor -> Tipo object, representa o valor atual do campo da tabela do BD, no seu objeto em memória. Como ele é do tipo object, aceita valores de qualquer
tipo de dados do .NET Framework.
Propriedade IsKey -> Indica se o campo pertence à chave primária da tabela.
Como métodos, temos apenas três construtores, um que somente inicializa um objeto do tipo TCampoCadastro e outro que inicializa já alimentando as propriedades
descritas nos parâmetros do construtor.
Isoladamente esta classe não serve para muita coisa, conforme vamos vendo nos exemplos, dificilmente criamos uma instância identificada para ela (utilizaremos ela
mais na forma de instância anônima utilizando como parâmetro de método).


Classe TBatchSQLItens:


É uma classe que armazena instruções SQL e parâmetros das mesmas para serem executadas em lote em uma única transação. Caso ocorra algum erro em alguma das instruções
do lote, os comandos anteriores do mesmo lote são desfeitos (rollback). Ela é utilizada em conjunto com os métodos de execução em lote da classe Conexao. Vamos ver suas
Propriedades e Métodos:

Propriedade instrucao -> tipo string, é o comando SQL a ser executado.

Propriedade parametros > tipo ArrayList, são os parâmetros da instrução SQL da propriedade "parametros".

Enumeração TTipoBancoDados

Representa os bancos de dados suportados pelo Framework SQL.

Enumeração GenericSQLDataTypes

Representa os tipos de dados genéricos para os bancos de dados. Esta enumeração está deprecada, ou seja, eu nunca a utilizei... mas pode servir para alguma coisa hehe

Enumeração OperacaoBD

Representa os tipos de operações que podemos fazer em um registro: Inserir, Atualizar e Excluir.

Classe Conexao

É onde a mágica toda se realiza :-)
Uma classe que abstrai os comandos normalmente utilizados para acesso a Bancos de Dados. Com ela, não precisaremos codificar a conexão, essas coisas!
Vamos às suas propriedades e métodos:

Propriedade TipoBD: propriedade de classe (ela é declarada como static) do tipo TTipoBancoDados, indica qual é o mecanismo de banco de dados utilizado, caso
se utilize mais de um provider ao mesmo tempo.
Só pode ser usado UM provider de cada vez, embora a classe possa ser compilada com vários providers, explico:
Notou que há em todo o código da classe Conexao vários comandos #if?
Nas primeiras versões, só havia o controle do BD utilizado através da propriedade TipoBD e para usar a classe era necessário ter TODOS os providers suportados referenciados na biblioteca de classes, senão o projeto não compilava. Foi aí que o pessoal de onde eu trampo me deu a idéia de utilizar DIRETIVAS DE COMPILAÇÃO, que fazem com que apenas os providers necessários sejam incluídos no programa.
Para definir as diretivas de compilação, no Visual Studio 2005, vá até a página de propriedades do projeto (botão direito sobre o projeto no Solution Explorer
-> Properties), vá até a guia BUILD e na caixa Conditional Compilation Symbols coloque as strings referentes aos providers utilizados, separados por espaço.
Com isso, apenas os providers realmente utilizados é que precisam ser referenciados no projeto da biblioteca de classes.

Campo protegido _SelectCommandSalvar -> É o comando select utilizado para pegar o esquema da tabela a ser utilizada por uma das sobrecargas do método Salvar().
Para isso, DEVE ser informado com o registro que queremos salvar, mesmo que ele ainda não exista no banco de dados. Veremos mais adiante :-)

Campos protegidos _InsertSQL, _UpdateSQL e _DeleteSQL -> Permite construir instruções SQL customizadas para a utilização em uma das sobrecargas do método Salvar().
Em certas ocasiões, não é produtiva a utilização do método Salvar() em sua sobrecarga sem parâmetros. Veremos o por quê disso!

Campo protegido _SelectSQL -> Instrução SQL utilizada para seleção de registros e colocá-lo em um objeto derivado de Conexao. Veremos o funcionamento quando descrevermos
o método Select, que utiliza este campo da classe.

Campo privado SQLParams -> Armazena uma coleção de parâmetros (é um ArrayList) para serem utilizados em comandos SQL pelos métodos da classe Conexao.
Como colocar valores neles? Veremos o método AddSQLParam!

Campo protegido ListaCamposTabela -> É uma coleção de objetos do tipo TCampoTabela. É a partir desta coleção de campos que o método Salvar() transfere os dados
do objeto no banco de dados, ou seja, faz a persistência.

Campo indexado this -> Este campo representa cada campo da tabela ou view do banco de dados é a forma mais direta de se acessar um item dentro da coleção
ListaCamposTabela descita acima. Cada campo poderá ser acessado pelo nome dele, o mesmo nome que está na instrução de select do método Select. Veremos o funcionamento
mais adiante.

Campo protegido BatchSQLItens -> Do tipo ArrayList, armazena as instruções SQL a serem executadas em um lote através do método execBatchSQL. Cada comando SQL
deverá ser informado em um item do ArrayList e não deve conter parâmetros, de preferência.

Campo protegido AdvancedBatchSQLItens -> É uma coleção de objetos do tipo TBatchSQLItens, armazena os conjuntos de instruções SQL e seus parâmetros para serem
executados através da função execAdvancedBatchSQL().

Campo protegido ForceQtdKeys -> Força a criação de chaves primárias virtuais (são criadas no DataSet) usadas em uma das sobrecargas do método Salvar.

Método protegido ClearSQLParams -> Limpa a coleção de parâmetros SQL, ou seja, o ArrayList SQLParams.

Método protegido CountSQLParams -> Conta os elementos do ArrayList SQLParams.

Método protegido AddSQLParam -> sobrecarga AddSQLParam(string pNomeParam, object pValor, ParameterDirection pDirecao): Adiciona um parâmetro à coleção SQLParams, onde é informado o nome do parâmetro,
com o sinal dependente do tipo de banco de dados utilizado (por ex, em SQL Server e Firebird cada variável tem o nome iniciado com @), o valor do parâmetro (em qualquer
tipo de dado) e a direção do parâmetro (Input, Output, etc).
-> sobrecarga (string pNomeParam, object pValor, ParameterDirection pDirecao, ref ArrayList pListaParametros) -> Adiciona um parâmetro ao ArrayList informado em
pListaParametros e NÃO adiciona na coleção SQLParams. É utilizada em conjunto com o método execAdvancedBatchSQL.

Método protegido getStrConexao -> Obtém a string de conexão a partir do arquivo de configuração do programa.
Para funcionar, o framework pede que uma entrada chamada "strConexao" seja criada na seção do arquivo de configuração da aplicação, ou seja,
em uma aplicação web no arquivo web.config e em uma aplicação Windows Forms no arquivo .config.

Campo protegido fMsgInfo -> Permite informar mensagem qualquer, seja mensagem de erro, de sucesso, etc para as classes que construímos. É um dos canais de comunicação
com a interface com o usuário.

Propriedade MsgInfo -> Expõe para outras classes o campo fMsgInfo, permitindo apenas a leitura do mesmo.

Método executaSQL -> Executa um comando SQL simples. O comando é informado no parâmetro isql. O parâmetro WithParams informa se a instrução possui parâmetros, por exemplo
"select * from MYTABLE where ID = @ID". Os parâmetros da instrução são obtidos a partir da coleção SQLParams. Retorna um valor booleano, indicando se a instrução foi
executada com sucesso ou não.
Portanto, o fluxo do programa deve parecer-se com:


   1: string sql = "select * from MYTABLE where ID = @ID";
   2: ClearSQLParams; //Ideal executar antes de cada instrução que requeira parâmetros
   3: AddSQLParam("@ID",5,ParameterDirection.Input); // 5 é o ID que queremos obter
   4: bool OK = executaSQL(sql,true);

Método execBatchSQL -> sobrecarga sem parâmetros: Executa o lote de instruções SQL contido na coleção BatchSQLItens em uma transação com isolação Serializable. Caso utilize esta sobrecarga, as instruções NÃO deverão ser parametrizadas. Retorna booleano indicando o sucesso da execução do lote.
Exemplo de fluxo de programa:


   1: string sqlDebito = "insert into MOVIMENTACAO (VALOR,TIPO_TRANSACAO,CONTA) values (5000,'D','00001/01');
   2: string sqlCredito = "insert into MOVIMENTACAO (VALOR,TIPO_TRANSACAO,CONTA) values (5000,'C','00002/02');
   3: BatchSQLItens.Add(sqlDebito);
   4: BatchSQLItens.Add(sqlCredito);
   5: bool TransferenciaOK = execBatchSQL();

PS: Quer exemplo mais clássico que esse para ilustrar transações? hehehe

-> sobrecarga execBatchSQL(bool WithParams): Faz a mesma coisa da sobrecarga acima, porém permite o uso de parâmetros das instruções SQL, porém tem uma limitação:
os parâmetros inseridos na coleção SQLParams DEVEM fazer parte de TODAS as instruções SQL do lote, exemplo:


   1: string sqlDebito = "insert into MOVIMENTACAO (VALOR,TIPO_TRANSACAO,CONTA) values (@VALOR,'D','00001/01');
   2: string sqlCredito = "insert into MOVIMENTACAO (VALOR,TIPO_TRANSACAO,CONTA) values (@VALOR,'C','00002/02');
   3: AddSQLParam("@VALOR",5000,ParameterDirection.Input);
   4: BatchSQLItens.Add(sqlDebito);
   5: BatchSQLItens.Add(sqlCredito);
   6: bool TransferenciaOK = execBatchSQL(true);

Agora, um outro caso:


   1: string sqlDebito = "insert into MOVIMENTACAO (VALOR,TIPO_TRANSACAO,CONTA) values (@DEBITO,'D','00001/01');
   2: string sqlCredito = "insert into MOVIMENTACAO (VALOR,TIPO_TRANSACAO,CONTA) values (@CREDITO,'C','00002/02');
   3: AddSQLParam("@DEBITO",5000,ParameterDirection.Input);
   4: AddSQLParam("@CREDITO",5000,ParameterDirection.Input);
   5: BatchSQLItens.Add(sqlDebito);
   6: BatchSQLItens.Add(sqlCredito);
   7: bool TransferenciaOK = execBatchSQL(true);

Se executarmos este trecho de código, a instrução execBatchSQL irá dar pau, pois são declarados dois parâmetros, porém a primeira instrução SQL possui apenas
o parâmetro "@DEBITO", e o banco de dados irá retornar uma mensagem de erro indicando inconsistência na contagem de variáveis declaradas no SQL com a contagem
das variáveis passadas como parâmetro. Para resolver a questão acima, criei o método execAdvancedBatchSQL().


Método execAdvancedBatchSQL -> Executa o lote de comandos SQL contido na coleção AdvancedBatchSQLItens em uma transação com isolação Serializable.
Este método permite a utilização de parâmetros declarados para cada comando SQL na coleção, com o auxílio da coleção AdvancedBatchSQLItens, AddSQLParam com a sobrecarga
que recebe um ArrayList e da classe TBatchSQLItens. Veja agora como eles foram feitos um para o outro :-)


   1: string sqlDebito = "insert into MOVIMENTACAO (VALOR,TIPO_TRANSACAO,CONTA) values (@DEBITO,@TP_TRANSACAO,'00001/01');
   2: ArrayList ParamsSQLDebito = new ArrayList();
   3: AddSQLParam("@DEBITO",5000,ParameterDirection.Input,ref ParamsSQLDebito);
   4: AddSQLParam("@TP_TRANSACAO",'D',ParameterDirection.Input,ref ParamsSQLDebito);
   5: AdvancedBatchSQLItens.Add(new TBatchSQLItens(sqlDebito,ParamsSQLDebito));
   6: string sqlCredito = "insert into MOVIMENTACAO (VALOR,TIPO_TRANSACAO,CONTA) values (@CREDITO,@TP_TRANSACAO,'00002/02');
   7: ArrayList ParamsSQLCredito = new ArrayList();
   8: AddSQLParam("@CREDITO",5000,ParameterDirection.Input,ref ParamsSQLCredito);
   9: AddSQLParam("@TP_TRANSACAO",'X',ParameterDirection.Input,ref ParamsSQLCreditop);
  10: AdvancedBatchSQLItens.Add(new TBatchSQLItens(sqlCredito,ParamsSQLCredito));
  11: bool TransferenciaOK = execAdvancedBatchSQL();

Agora cada instrução SQL pode ter os seus parâmetros, que eles são "mesclados" com as instruções SQL respectivas através da classe TBatchSQLItens, que em seu construtor
recebe um parâmetro string, que é a instrução SQL a ser executada e um ArrayList com os parâmetros populados através do método AddSQLParam.
Embora o parâmetro @TP_TRANSACAO tenha o mesmo nome nas duas instruções SQL, devemos adicioná-lo às duas coleções de parâmetros, pois possuem valores diferentes,
e cada comando SQL não enxerga a os parâmetros SQL de outras coleções (declaradas para outros comandos).
Método protegido getScalar -> Retorna o valor da primeira coluna da primeira tupla da instrução SQL informada no parâmetro isql (ou seja, isql DEVE ser um SELECT).
A instrução não deve conter parâmetro. Esta rotina retorna um tipo object, que pode ser feito casting para outros tipos de dados, ou retornado com string através
do método .ToString() da função. Útil para pesquisarmos por um único valor.


Método protegido getTable -> Retorna um resultset em um DataTable representado pelas tuplas da instrução SQL enviada no parâmetro isql. A instrução SQL pode ser
parametrizada, com os parâmetros adicionados pela função AddSQLParam() e caso isso ocorra, o parâmetro WithParams deve ser passado como true. Exemplo:


   1: ClearSQLParams();
   2: AddSQLParam("@SALARIO",5000,ParameterDirection.Input);
   3: DataTable dt = getTable("select * from FUNCIONARIOS where SALARIO > @SALARIO",true);

Eu utilizo essa função pra k7 ;-)

Método fillDropDownList -> Preenche um Drop Down List. O dropdown list a ser preenchido é o do namespace System.Web.UI.WebControls, portanto, serve mais para aplicações web.
Como parâmetros, temos o drop down list a ser preenchido, a instrução SQL que irá gerar os dados, o nome do campo de texto (o que aparecerá para o usuário selecionar)
e o nome do campo de valor.

Método executeStoredProcedure -> Executa uma stored procedure e retorna uma coleção de objetos do tipo TCampoCadastro representando os parâmetros da stored procedure.
Como parâmetros do método, são informados o nome da stored procedure a ser executada e se ela possui parâmetros. Cada parâmetro, seja de entrada ou de saída, deve
ser adicionado através da função AddSQLParam, veja exemplo:


   1: ClearSQLParams();
   2: AddSQLParam("@NUM1",1,ParameterDirection.Input);
   3: AddSQLParam("@NUM2",1,ParameterDirection.Input);
   4: AddSQLParam("@RESULTADO",0,ParameterDirection.Output);
   5: List execucao = executeStoredProcedure("SP_SOMA",true);
   6: double resultado = (double)execucao[2].Valor;

Importante saber a ordem em que os parâmetros foram adicionados, pois é através de seu índice que podemos pegar o valor dos parâmetros de retorno da SP.
Caso dê erro na execução, ele retornara null.


Método Salvar -> Este é o método que persiste um objeto derivado de Conexao (como veremos, a classe Conexao não é instanciada) no banco de dados.
Isso pode ser feito de várias formas distintas, dependendo da lógica de seu programa. Vamos ao que está implementado na classe Conexao:
-> sobrecarga sem parâmetros: O método Salvar() guarda cada valor armazenado na coleção ListaCamposTabela em um registro do Banco de Dados. Note que este método
é declarado como público e virtual. Sendo assim, parte dele, mais precisamente a alimentação da coleção ListaCamposTabela é feita a partir da sobreescrita do método
Salvar() na subclasse. Note que a classe Conexao não conhece o esquema da tabela onde os dados serão salvos, ou seja, não sabe em que tabela, quais os campos, etc.
Este conhecimento ela também adquire da subclasse, como veremos a seguir:


   1: public class Cadastro : Conexao
   2: {
   3:     private int _Codigo = 0;
   4:     private string _Nome = "";
   5:  
   6:     public override bool Salvar()
   7:     {
   8:         ListaCamposTabela.Clear();
   9:         _SelectCommandSalvar = "select CODIGO,NOME from CADASTRO where CODIGO = " + _Codigo.ToString();
  10:         ListaCamposTabela.Add(new TCampoCadastro("CODIGO",_Codigo,true));
  11:         ListaCamposTabela.Add(new TCampoCadastro("NOME",_Nome",false));
  12:         return base.Salvar();
  13:     }
  14: }

Como vimos, através da propriedade _SelectCommandSalvar ele toma conhecimento da tabela onde o registro será salvo, ou seja, o objeto Cadastro será persistido
no BD. Mas, como eu vou saber se é para inserir ou atualizar?

Na propriedade _SelectCommandSalvar, selecionamos no banco de dados o registro que queremos salvar, ou seja, colocamos as chaves primárias na cláusula Where
do comando SQL. Daí tiramos uma limitação desta sobrecarga do método Salvar: a tabela DEVE ter uma chave primária, seja simples ou composta.
Se o registro informado pelas chaves não existir na tabela, ele insere, caso contrário, ele é atualizado com os novos valores.
Então, a sobreescrita do método ficou assim: Limpeza da coleção ListaCamposTabela, definição da instrução SQL de checagem do registro e informação do esquema da tabela,
Adição de objetos TCampoCadastro com cada campo que queremos salvar e execução da rotina da classe base Conexao.
Uma desvantagem deste método é que para ele saber se o registro deve ser inserido ou atualizado, ele primeiro faz uma pesquisa na tabela para depois salvar efetivamente.
Uma vantagem, é que através dele podemos salvar campos do tipo blob: basta que a propriedade Valor do objeto TCampoCadastro adicionado à coleção ListaCamposTabela
seja do tipo Stream.

-> sobrecarga Salvar(OperacaoBD operacao, bool HaveParams): Permite a confecção de instruções SQL customizadas para cada operação (Insert, Update e Delete).
Estas instruções são definidas das propriedades _InsertSQL, _UpdateSQL e _DeleteSQL.
O parâmetro operacao indica a operação a ser realizada, e HaveParams indica se as instruções SQL serão parametrizadas.
Como na interface com o usuário não utilizamos comandos SQL segundo esta metodologia, geralmente eu construo um método Salvar na subclasse somente passando como parâmetro a operação a ser executada, e dentro dele chamo esta sobrecarga.

Método Select -> Utilizado para obter um registro do banco de dados e representar cada campo da tabela em um dicionário de campos da classe Conexao.
É o "contrário" do método Salvar: o Salvar persiste um objeto no BD, e o Select recupera um registro do BD em um objeto, ou seja, ele alimenta a coleção
ListaCamposTabela e ainda permite acessá-la usando o nome do campo como índice. O parâmetro HaveParams indica se _SelectSQL é parametrizado. Veja um exemplo:


   1: public class Cadastro : Conexao
   2: {
   3:     private int _Codigo = 0;
   4:     private string _Nome = "";
   5:  
   6:     public void SelectByID(int Codigo)
   7:     {
   8:         this._SelectSQL = "select CODIGO,NOME from CADASTRO where CODIGO = @CODIGO");
   9:         ClearSQLParams();
  10:         AddSQLParam("@CODIGO",Codigo,ParameterDirection.Input);
  11:         Select();
  12:         _Codigo = (int)this["CODIGO"];
  13:         _Nome = (string)this["NOME"];
  14:     } 
  15: }

Finalmente, método Dispose() -> Libera o objeto da memória.

Pronto, estes são os componentes da minha famosa "Classe de Conexão", que me ajudam a beça e poupam um trabalho danado... Reparou nos exemplos que não utilizamos
nenhum objeto específico de um provider de banco de dados? Executamos instruções SQL, stored procedures, selecioamos dados...
No próximo artigo publicarei um programa exemplo totalmente funcional, rodando Firebird e SQL Server demonstrando a classe de conexão.
Um grande abraço a todos e até!

Leia o restante deste post...

Sobre o NeoMatrix Tech

Meu blog para assuntos profissionais, ligado com tecnologia.
Dicas de programação (grande parte de C# e ASP.NET, mas não limitado a essa plataforma :-) ), dicas de utilitários, análises de equipamentos e serviços, resenhas sobre sites que eu visito, relacionados com tecnologia, opinião sobre mercado de trabalho, metodologias de desenvolvimento, comportamento no mundo tecnológico...

NeoMatrix Light

  © Blogger templates ProBlogger Template by Ourblogtemplates.com 2008 - Editado e configurado por Leonel F.

Voltar ao TOPO