23 de março de 2012

Trabalhando com rotinas T-SQL no SQL Server

O SQL (Structured Query Language) é muito mas que apenas uma linguagem para manutenções num banco de dados. Ela não se restringe às quatro operações básicas (INSERT, DELETE, UPDATE e SELECT) ou àquelas funções para gerenciar tabelas ou transações. O SQL Server da Microsoft, por exemplo, implementa uma extensão do padrão SQL chamada T-SQL (Transact SQL) que permite construir funções complexas. Elas combinam queries com elementos típicos das linguagens de programação comuns, fornecendo uma sintaxe que inclui variáveis, loops e controle de fluxo.

Com essa linguagem, você pode construir Triggers, Stored Procedures, Functions ou simplesmente executar comandos pelo SQL Server Management Studio (função antigamente disponível no Query Analyser). Neste post, vou usar essa ferramenta para mostrar alguns dos recursos do T-SQL.

Para montarmos o exemplo básico, considere que vamos gerar uma lista de preços baseada nas informações contidas numa tabela chamada LISTAPR, criada de acordo com o comando a seguir:
-- Em SQL Server
create table LISTAPR
(
ID int not null,
PRODUTO varchar(25) null,
DESCRICAO varchar(255) null,
PRECO numeric(15,4) null,
DATA_VLD smalldatetime null
)

Como toda linguagem, o T-SQL possui comandos de declaração de variáveis para armazenar valores ao longo do programa. Basicamente, os tipos de dados permitidos são os mesmos que são válidos para criação de campos num tabela, com exceção dos tipos que representam BLOBs (text, ntext e image). A declaração em si é feita com a palavra chave DECLARE seguida de um arroba (@) acompanhando o nome da variável e, para finalizar, o tipo de dado que essa variável será capaz de armazenar. Exemplos:
DECLARE @produto varchar(25)
DECLARE @descricao varchar(255)
DECLARE @preco numeric(15,4)
DECLARE @linha varchar(300)
DECLARE @nro_regs int

SELECT @nro_regs = 0

A forma como os valores são recuperados do banco de dados nos obriga a declarar variáveis para os campos que pretendemos manipular. Assim, no trecho acima eu declarei uma variável para cada campo que será impresso. Como se vê também, o comando SELECT pode ser usado para atribuir manualmente valores para as variáveis.

Há um tipo especial de variável para ser usado na obtenção de um result set (lista de registros). O nome desse tipo é CURSOR e é através dele que estabelecemos quais dados serão recuperados do banco, qual o tipo de navegação será permitida nos registros (apenas avançar ou ir pra frente e pra trás) e também se será possível atualizar os dados. Para o cenário proposto neste post, precisaremos de uma coleção read-only que contenha todos os registros da tabela LISTAPR que estejam dentro da validade. A declaração abaixo atende esses requisitos:
DECLARE lista CURSOR SCROLL FOR
SELECT produto, descricao, preco
FROM LISTAPR
WHERE DATA_VLD > GETDATE() OR DATA_VLD IS NULL

O tipo da navegação é irrelevante aqui mas eu inclui a palavra SCROLL na declaração, possibilitando a navegação pelos registros em qualquer direção que queiramos. Outro detalhe é que eu estipulei quais campos eu quero recuperar. Isso é uma boa prática pois, conforme veremos adiante, deixa claro quais campos corresponderão a quais variáveis.

Agora, podemos abrir o cursor criado, navegar pela lista de registros obtidos, processar os dados conforme necessário e, finalmente, encerrar o cursor. O quadro abaixo traz o código com os passos citados:
OPEN lista
-- Primeiro registro
FETCH FIRST FROM lista INTO @produto, @descricao, @preco

WHILE @@FETCH_STATUS = 0
BEGIN
-- O processamento do registro atual será feito aqui

-- Próximo registro
FETCH NEXT FROM lista INTO @produto, @descricao, @preco
END

CLOSE lista
DEALLOCATE lista

Vamos destrinchar esse código. O comando OPEN apenas abre o cursor, submetendo a query ao banco de dados. O comando FETCH FIRST posiciona no primeiro registro e transfere para as variáveis os valores obtidos. Veja que a ordem das variáveis na cláusula INTO segue a mesma ordem estabelecida na cláusula SELECT.

A instrução WHILE é um laço tradicional, repetindo os comandos entre o BEGIN e o END enquanto o valor da expressão após o WHILE for verdadeira. Neste caso, @@FETCH_STATUS é uma variável nativa do T-SQL. Ela é afetada pela execução de comandos FETCH, assumindo valor 0 (zero) quando há um registro válido posicionado. Com isso, o comando FETCH NEXT, é executado enquanto ainda houver registros a recuperar no cursor.

Para encerrar, o cursor é fechado com o comando CLOSE e os recursos usados por ele são devolvidos ao sistema através do comando DEALLOCATE. Variáveis do tipo CURSOR são as únicas que requerem a desalocação explícita.

Ficou faltando a parte do processamento interno do laço. Como planejamos simplesmente imprimir os valores das variáveis, podemos usar o comando PRINT para isso. Ele aceita um texto como parâmetro, de modo que teremos que fazer a conversão dos tipos numéricos antes de imprimir.
SELECT @nro_regs = @nro_regs + 1, @linha = ''

SELECT @linha = @linha + @produto + REPLICATE (' ', 21 - LEN (@produto))
SELECT @linha = @linha + @descricao + REPLICATE (' ', 21 - LEN (@descricao))
SELECT @linha = @linha + STR (@preco, 10, 2)

PRINT @linha

Cada chamada ao PRINT gera uma nova linha na janela de saída do Management Studio, razão pela qual concateno os valores num único texto antes de imprimí-lo. Assim, cada produto terá sua própria linha. A ginástica com o REPLICATE e o LEN é para garantir que produto e descrição apareçam sempre com a mesma quantidade de caracteres, mantendo o resultado alinhado, mais fácil de ler. O STR converte o preço em um texto para a impressão.

É claro que, dada a simplicidade do exemplo, poderíamos ter resolvido o problema apenas executando a query. Mas o objetivo aqui era mostrar a flexibilidade da linguagem já que ela permite conter cursores aninhados, trabalhar com decisões de fluxo (instrução IF), cálculos complexos e outras interações. Aqui na ABC71, nós aproveitamos essa flexibilidade do T-SQL mesclada a queries que obtêm informações sobre tabelas do banco de dados para gerar certos tipos de codigo fonte auxiliares de nosso ERP.

13 de março de 2012

Design Patterns com Delphi: Visitor - Parte II

No último post, eu apresentei o conceito do Design Pattern Visitor usando para isso um diagrama UML com um exemplo prático da aplicabilidade do padrão. O exemplo consiste na representação de um Produto Acabado composto por uma lista de recursos (as matérias primas, máquinas e instruções) usados para fabricá-lo. O objetivo é permitir aplicar ao produto operações externas cujo resultado depende da aplicação da operação em cada parte que compõe a estrutura. No exemplo, há duas operações: uma que exporta a arquitetura do produto em formato XML e outra que faz a impressão dessa estrutura.

Neste post, mostro uma sugestão de implementação em Delphi para o exemplo do Visitor. Para facilitar a referência, publico novamente o diagrama que o retrata:
Diagrama UML para o padrão Visitor
O primeiro passo é definir as classes que representam o propósito do sistema, estabelecendo as regras de negócio que constituirão o cerne dele. No nosso caso, essas classes são o recurso de produção, suas heranças e o produto acabado. O quadro abaixo mostra as declarações delas, que são bastante simples :
{ Recursos para a produção }
TWRecursoProducao=class
public
_Nome: String;

Constructor Create (ANome: String);virtual;
procedure Accept (AOper: TWOperacoesPA);virtual;
end;

TWMateriaPrima=class(TWRecursoProducao)
public
_CProd: String;
_Qtde: Double;

Constructor Create (ANome: String);override;
procedure Accept (AOper: TWOperacoesPA);override;
end;

TWMaquina=class(TWRecursoProducao)
public
_Cod: String;
_Tempo: Double;
_TempoSetup: Double;

Constructor Create (ANome: String);override;
procedure Accept (AOper: TWOperacoesPA);override;
end;

TWRoteiro=class(TWRecursoProducao)
public
_Cod: String;
_Texto: String;

Constructor Create (ANome: String);override;
procedure Accept (AOper: TWOperacoesPA);override;
end;

{ Produto Acabado }
TWProdutoAcabado=class
protected
_MatPrimas : TObjectList;
_Maquinas : TObjectList;
_Roteiro : TWRoteiro;
procedure ClearListaRec (ALista: TObjectList);

public
_Nome: String;

Constructor Create (ANome: String);
Destructor Destroy; override;

procedure ClearRecursos;
procedure AddMatPrima (ARecurso: TWRecursoProducao);
procedure AddMaquina (ARecurso: TWRecursoProducao);
procedure SetRoteiro (ARoteiro: TWRoteiro);

procedure DoOperacao (AOper: TWOperacoesPA);
end;
Veja que a classe que representa o produto acabado (TWProdutoAcabado) possui membros do tipo TObjectList e também um roteiro separado. Esses membros armazenarão os recursos necessários para fabricar o produto acabado. Neste exemplo, as classes que compõem a agregação são heranças de uma mesma classe base - a TWRecursoProducao - mas o padrão Visitor não faz essa exigência. Assim, ele ainda é aplicável mesmo quando a agregação é composta por classes que não guardam relação entre si. Então, usei variáveis separadas pra cada tipo de recurso apenas para deixar clara essa possibilidade.

Repare ainda na função Accept introduzida na classe base de recursos. É ela quem define a família de classes como Visitable, determinando que os recursos de produção que elas representam poderão ser visitados por qualquer operação externa herdada de TWOperacoesPA. Na verdade, Accept simplesmente solicitará à operação que "visite" o recurso em questão, o que fará com que a operação seja aplicada ao recurso :
procedure TWMateriaPrima.Accept (AOper: TWOperacoesPA);
begin;
inherited;
AOper.Visit (Self);
end;

procedure TWMaquina.Accept (AOper: TWOperacoesPA);
begin;
inherited;
AOper.Visit (Self);
end;

procedure TWRoteiro.Accept (AOper: TWOperacoesPA);
begin;
inherited;
AOper.Visit (Self);
end;
A seguir, podemos definir a hierarquia de classes que implementarão as operações externas nos recursos de produção. Tais classes exercerão o papel de Visitors:
TWOperacoesPA=class
public
procedure InitOper(AInfo: TObject);virtual;
procedure TermOper;virtual;

procedure Visit (pRecurso: TWMateriaPrima);overload;virtual;
procedure Visit (pRecurso: TWMaquina);overload;virtual;
procedure Visit (pRecurso: TWRoteiro);overload;virtual;
end;

TWRecursoSaveToXml=class(TWOperacoesPA)
protected
_Xml : String;
public
procedure InitOper(AInfo: TObject);override;
procedure TermOper;override;

procedure Visit (pRecurso: TWMateriaPrima);overload;override;
procedure Visit (pRecurso: TWMaquina);overload;override;
procedure Visit (pRecurso: TWRoteiro);overload;override;

function GetXML : String;
end;
A operação para imprimir o produto acabado é bastante similar à de salva para XML e, por isso, eu a omiti do quadro.

Veja que a classe TWOperacoesPA, que é base para todas as operações, possui 3 métodos Visit sobrecarregados (overload). Cada um deles trata um tipo diferente de recurso, o que, na prática, nos permite adequar o comportamento da operação. Isso quer dizer que a operação será realizada de um modo coerente com o tipo do recurso.

Um outro detalhe nessa classe é a função InitOper. Ela é desenhada para realizar procedimentos iniciais da operação, aceitando um parâmetro genérico do tipo TObject com informações que façam sentido para a operação. Por exemplo, pode representar uma impressora para a operação de Imprimir ou um objeto DOM para a exportação em formato XML.

O trecho de código abaixo mostra as funções de inicialização e encerramento da operação de exportação para XML. Também retrata a versão da função Visit sobrecarregada para os tipos de recurso "Matéria Prima" e "Máquina":
procedure TWRecursoSaveToXml.InitOper(AInfo: TObject);
var lProd: TWProdutoAcabado;
begin
inherited;

{Abre a tag raiz para o XML }
lProd := AInfo As TWProdutoAcabado;
_XML := '<?xml version="1.0" encoding="ISO-8859-1"?>' + #13#10;
_XML := _XML + '<produtoAcabado name="' + lProd._Nome + '">' + #13#10;
end;

procedure TWRecursoSaveToXml.TermOper;
begin
{ Fecha a tag XML raiz }
_XML := _XML + '</produtoAcabado>';
inherited;
end;

procedure TWRecursoSaveToXml.Visit (pRecurso: TWMateriaPrima);
begin
inherited;

{ Versão específica dessa operação p/ o recurso "Matéria Prima" }
_XML := _XML + ' <materiaPrima>' + #13#10;
_XML := _XML + ' <codigo>' + pRecurso._CProd + '</codigo>' + #13#10;
_XML := _XML + ' <nome>' + pRecurso._Nome + '</nome>' + #13#10;
_XML := _XML + ' <qtde>' + FormatFloat ('#,##0.00', pRecurso._Qtde) + '</qtde>' + #13#10;
_XML := _XML + ' </materiaPrima>' + #13#10;
end;

procedure TWRecursoSaveToXml.Visit (pRecurso: TWMaquina);
begin
inherited;
{ Versão específica dessa operação p/ o recurso "Máquina" }
_XML := _XML + ' <maquina>' + #13#10;
_XML := _XML + ' <codigo>' + pRecurso._Cod + '</codigo>' + #13#10;
_XML := _XML + ' <nome>' + pRecurso._Nome + '</nome>' + #13#10;
_XML := _XML + ' <setup>' + FormatFloat ('#,##0.00', pRecurso._TempoSetup) + '</setup>' + #13#10;
_XML := _XML + ' <tempo>' + FormatFloat ('#,##0.00', pRecurso._Tempo) + '</tempo>' + #13#10;
_XML := _XML + ' </maquina>' + #13#10;
end;
Por fim, podemos implementar a função que aplica a operação ao produto acabado. Por definição, isso implica aplicar a mesma operação sobre os recursos que compõe esse produto acabado. Por isso, teremos que passar por todos os recursos, chamando a função Accept para determinar como a operação deve ser executada em cada um deles:
procedure TWProdutoAcabado.DoOperacao (AOper: TWOperacoesPA);
var i : integer;
lMatPrima: TWMateriaPrima;
lMaquina : TWMaquina;
begin
AOper.InitOper (Self);

{ Aplica a operação a cada matéria prima da lista }
for i := 0 to _MatPrimas.Count - 1 do
begin
lMatPrima := _MatPrimas.Items[i] As TWMateriaPrima;
lMatPrima.Accept (AOper);
end;

{ Aplica a operação a cada máquina da lista }
for i := 0 to _Maquinas.Count - 1 do
begin
lMaquina := _Maquinas.Items[i] As TWMaquina;
lMaquina.Accept (AOper);
end;

{ Aplica a operação no roteiro, se houver um }
if (_Roteiro <> Nil) then
_Roteiro.Accept (AOper);

AOper.TermOper;
end;

{ ... }

var lOperacao : TWRecursoSaveToXml;
lProd : TWProdutoAcabado;
lXml : String;
begin
lProd := TWProdutoAcabado.Create ('Computador');

lOperacao := TWRecursoSaveToXml.Create;

lProd.DoOperacao(lOperacao);

{ Publica o resultado da operação }
lXml := lOperacao.GetXML ();
{ ... }
end;

A parte inferior do quadro demonstra como se dá a aplicação de uma operação.

O projeto com esse exemplo pode ser salvo a partir desse link. Ele foi criado em Delphi 2005 mas deve ser possível compilá-lo em outras versões do IDE sem problemas.

27 de fevereiro de 2012

Design Patterns com Delphi: Visitor - Parte I

Coleções de objetos são uma das estruturas mais comumente usadas na construção de programas de computador. Elas podem aparecer na forma de listas, vetores ou em agregações e composições, onde diferentes tipos de objeto são mantidos juntos para representar um objeto mais complexo.

Um dos propósitos que nos leva a agruparmos objetos numa mesma estrutura é facilitar a execução de operações envolvendo as partes que constituem esse objeto. Ou seja, operações nas quais percorreremos todos os elementos e aplicaremos a cada um deles a operação desejada. Nesse processo, a existência de objetos de tipos distintos deve ser levada em conta porque uma mesma operação pode ter significado diferente, dependente do tipo de objeto. Isto nos leva a ter que escolher a versão correta da operação que deve ser executada em cada elemento. Por exemplo, considere que você tem uma classe representando a lista de recursos necessários para fabricar um produto. A lista inclui as matérias primas, as máquinas envolvidas e até roteiros descrevendo o processo de fabricação. Uma operação que imprima a estrutura terá que considerar as diferenças entre cada uma dessas partes pois elas têm informações específicas distintas.

Em princípio, parece que uma herança simples pode resolver a questão. Mas, e se for necessário acrescentar novos tipos de operação, com nomes e parâmetros diferentes - salvar em formato XML, por exemplo ? Teríamos que alterar a estrutura da agregação para que ela comporte a nova operação.

O intuito do Design Pattern comportamental Visitor é facilitar a manutenção desse tipo de estrutura heterogênea. Ele nos obriga a manter separada a estrutura de um objeto e os algoritmos das operações que podem ser aplicadas sobre seus componentes. Com isso, podemos introduzir novas operações à estrutura sem que seja preciso modificar as classes onde essas operações serão aplicadas. Em outras palavras, o que o Visitor propõe é transferir a implementação das operações, removendo-as das classes que compõem a agregação e passando-as para classes próprias. Com isso, preserva-se a estrutura original tanto da agregação quanto das partes que a compõem.

O diagrama abaixo mostra um exemplo prático da aplicabilidade desse padrão. Nele, um Produto Acabado é composto de uma lista de recursos, incluindo as matérias primas, máquinas e instruções para a fabricação dele. São definidas duas operações externas : uma para exportar a arquitetura do produto para XML e outra para imprimí-la.
Diagrama UML para o padrão Visitor
De acordo com o papel que exercem na implementação de uma solução para o padrão Visitor, as classes envolvidas são formalmente conhecidas pelos nomes que seguem:
O Visitor é uma abstração que define quais classes de objetos poderão ser submetidas às operações que serão disponibilizadas. Ele faz isso ao introduzir métodos sobrecarregados, um para cada tipo de elemento da agregação que sofrerá a operação. Por convenção, este método é nomeado Visit. No diagrama, este é o papel da classe TWOperacoesPA.

A implementação das operações em si é feita em classes chamadas Concrete Visitor. Essas classes são heranças simples do Visitor, o que significa que elas terão que fornecer sua própria versão de cada método Visit definido na classe base. Com isso, a operação tratará de forma condizente os diferentes componentes da coleção. No exemplo, as classes TWRecursoImprimir e TWRecursoSaveToXml fazem esse papel.

Chamamos de Visitable (ou Element) a classe que define um método Accept capaz de receber uma instância da classe de operação e executar a versão correta do método Visit dessa operação. Ela, portanto, serve de ponto de entrada da execução, definindo o comportamento esperado para todas as heranças que poderão ser "visitadas". São às vezes chamadas de Element por coincidir com a classe base dos elementos que compõem uma agregação. A classe que exerce esse papel no diagrama de exemplo é a TWRecursoProducao.

Os Concrete Visitable (ou Concrete Element) são as classes que efetivamente implementam o método Accept, direcionando a execução da operação para o tipo de objeto que representam. Isto é, chamando o método Visit correto da operação passada como parâmetro. As classes TWMateriaPrima, TWMaquina e TWRoteiro são exemplos disso no diagrama.

O Object Structure é a classe que contém a coleção de elementos sobre os quais uma operação será executada. Ela, então, terá que prover um meio para que a coleção seja percorrida de modo que todos os seus componentes possam ser submetidos à operação desejada. Este é o papel da classe TWProdutoAcabado no diagrama.

O Client é responsável por manter a instância tanto do Object Structure quanto da operação que será executada. Ela também navega os elementos da estrutura, executando em cada um a operação solicitada.

O padrão Iterator também é projetado para permitir a navegação entre elementos que compõem um objeto. A diferença é que no Iterator os elementos devem ser herança de uma mesma classe enquanto no Visitor isso não é obrigatório.

O cenário onde o padrão Visitor é aplicável se assemelha ao uso de interfaces, já que em ambos os casos, uma estrutura externa estabelece as operações que a classe é capaz de executar. No entanto, interfaces forçam a implementação das operações dentro da classe ao invés de fazê-lo em classes separadas, como no Visitor. Obviamente, ambos os recursos podem ser utilizados em conjunto para produzir uma solução mais flexível.

Alguns críticos alegam que o Visitor fere o encapsulamento das classes ao obrigar que parte de seu funcionamento seja delegada a outra classe. Mas, podemos encarar essa classe extra como uma operação distinta, minimizando ou até mesmo eliminando o efeito da quebra de encapsulamento.

No próximo post eu mostro uma sugestão de implementação desse pattern usando o Delphi.

Mais Informações
Posts sobre Design Patterns

14 de fevereiro de 2012

Aplicando recursos de localização em Asp.Net

por Tiago Oliveira*

Embora seja referenciado por uma palavra um tanto quanto estigmatizada - globalização - o fato é que o mundo ficou mesmo menor e os fabricantes de aplicações também passaram a levar isso em consideração. Ao planejar a construção de programas e sites, eles têm agora que incluir o conceito de localização na arquitetura, de uma forma que a aplicação se adeque à linguagem preferida pelo usuário sem que seja necessária uma reengenharia nas linhas de código.

O ASP.NET possui recursos de localização para que possamos fazer aplicações globalizadas, ou seja, para que a aplicação se adeque à região onde ela está sendo executada. A configuração utilizada pelo ASP.NET é a informada no navegador (no caso do IE, está no seguinte caminho : Opções de Internet/Geral/Idiomas), sendo considerado tanto o Idioma quanto a Região estipulados. Segue abaixo um exemplo de configuração:
en-US – Inglês região dos EUA.
pt-BR – Português região do Brasil.
As duas primeiras letras no código representam o Idioma e as duas últimas a região.

Agora vamos aplicar os recursos de localização disponíveis no ASP.NET para modificar esse comportamento padrão e fornecer um mecanismo para que o usuário opte pelo idioma de sua preferência na própria aplicação. Adicione um WebForm contendo 2 botões que, no caso, irão representar os possíveis idiomas suportados para visualização da aplicação : português e inglês.

Adicione um outro WebForm contendo alguns componentes Label, TextBox, etc. Para aplicar a localização às páginas da aplicação, o ASP.NET utiliza arquivos de Resources; por isso, vamos adicionar um arquivo de resource em nossa aplicação: clique no WebForm e selecione na guia Tools do Visual Studio a opção “Generate Local Resource”. Ao escolher essa opção. um arquivo de extensão .RESX com o mesmo nome da página será gerado dentro de uma pasta chamada App_LocalResources que será automaticamente adicionada ao seu projeto.

Dentro desse arquivo .RESX existe a relação de todos os componentes que comportam o recurso de localização, juntamente com a descrição dos campos. No nosso exemplo, esse arquivo irá representar a versão em português da aplicação; para gerarmos o arquivo contendo a versão em inglês, faça uma cópia do arquivo NomeDaSuaPagina.aspx.resx gerado pelo Visual Studio e altere o nome para NomeDaSuaPagina.aspx.en-US.resx. Nesse novo arquivo, faça a tradução dos campos para o Inglês.

No WebForm onde colocamos os 2 botões para identificar os idiomas, iremos passar a localização através de Query String, conforme código abaixo associado ao clique de cada um dos botões:
protected void ImageButton1_Click(object sender, ImageClickEventArgs e)
{
Response.Redirect("Conteudo.aspx?Language=pt-BR");
}

protected void ImageButton2_Click(object sender, ImageClickEventArgs e)
{
Response.Redirect("Conteudo.aspx?Language=en-US");
}
Na página Conteudo.aspx (representando a página principal da aplicação), precisamos inicializar a cultura de exibição para considerar a que passamos por parâmetro e não aquela que está configurada no navegador. Para isso precisamos sobrescrever o método InitializeCulture da página e ajustar a variável interna UICulture, que é a responsável por controlar o idioma em que a aplicação está sendo exibida:
protected override void InitializeCulture()
{
UICulture = Request.QueryString["Language"];
base.InitializeCulture();
}
Ao exibir os textos da página, o ASP.Net passará a considerar automaticamente o conteúdo do arquivo .RESX referente ao idioma e região escolhidos.

* Tiago Oliveira é desenvolvedor na ABC71, onde trabalha com os módulos fiscais, de faturamento e de vendas.