View
312
Download
6
Category
Preview:
Citation preview
267
268
Até aqui somente conhecemos uma forma de atualizar a base de dados de nossa aplicação: as transações.
Como sabemos, uma transação determina a ou as tabelas requeridas para armazenar sua informação. Por suavez, ao ser gerada, se converte no programa que implementa a lógica de inserção, eliminação e modificaçãode dados nessas tabelas, de forma totalmente transparente para o programador. Também a transação permitedefinir todas as regras de negócio que os dados associados deverão cumprir. O programa gerado se encarregade executá-las. Assim, se em uma transação temos uma ou várias regras Error, que se disparam ao satisfazerdeterminadas condições, essas regras estarão incluídas no código gerado, e não se permitirá atualizar a basede dados até as condições que disparam essas regras deixarem de satisfazer para os dados que estãomanipulando nessa oportunidade.Assim mesmo, como vimos, as transações asseguram o controle de integridade referencial, muito importantepara assegurar a consistência da base de dados.
Mas as transações não cobrem todas as necessidades referente a atualização de dados. Também seránecessário uma forma não interativa, batch, para realizar atualizações sobre as tabelas.
Para isso existem duas alternativas: atualização utilizando o que se conhece como Business Component,absolutamente relacionado com as transações, ou utilizar comandos específicos para tal fim dentro de objetosde tipo Procedure.
Depois veremos que os Business Components permitem grande flexibilidade, visto que permitem atualizar abase de dados de qualquer forma: tanto interativa como não interativa.
Em seguida introduziremos os Business Components e depois os comandos de atualização direta dentro dosProcedimentos.
269
270
271
272
Por default a propriedade Business Component estará apagada. Isso significará que a lógica de negócio dadapela transação, somente será utilizada dentro da própria transação, e não ao se criar um tipo de dados com omesmo nome da transação, Bill, que permita encapsular a lógica da transação para utilizá-la a partir de outrosobjetos.
273
274
Observe que se consideram alguns membros da estrutura da variável &bill, Business Component, e depois seexecuta o método Save (inexistente em SDTs).
Isto é equivalente ao que ocorria de forma interativa se o usuário final insere esses mesmos valores nosatributos correspondentes do form da transação, e depois pressionar o botão ‘Confirm’.
Portanto, da mesma forma que ocorre com a transação, deverão ser disparadas as regras e serem realizadasos controles de integridade referencial. O que acontecerá com esse conjunto de dados se tentar gravarmediante a transação? A gravação é impedida, devido que está cumprindo a condição da segunda regra deerror. Por outro lado, se não existisse na tabela CUSTOMER um cliente com identificador 3456, a integridadereferencial vai falhar e assim tampouco deixaria de ingressar o registro na tabela BILL da base de dados.
275
Antes de executar o método Save do Business Component, o valor de BillId na estrutura de &bill será vazio.Portanto, uma vez que se execute o Save, como o atributo BillId era autonumber na tabela BILL, se executa apropriedade a nível da tabela e o registro será inserido com o número seguinte ao último dado.
No caso de BillDate, da mesma forma, como não se atribui valor na estrutura de &bill, antes do Save estarávazio, mas quando este se execute, será disparada a lógica da transação, e em particular a regra Default(BillDate, &today ) declarada. Portanto no registro inserido na base de dados, o valor do atributo BillDatecorresponderá a data de hoje.
Para BillAmount poderíamos fazer a mesma análise. Mas a diferença do caso anterior, não tem regra queatribui valor, pela qual se insere o registro com valor vazio neste atributo.
Se atribuir valor para &bill.CustomerName não será considerado na hora da inserção, visto queCustomerName é um atributo inferido, e não tem regra Update que permita modificá-lo.
O mesmo acontece para atributos definidos a nível da estrutura da transação mas que foram fórmulas. Isto é,são atributos virtuais na transação, e também são a nível do Business Component. Seu valor é disparado.
276
277
Para gerenciar os erros terá que definir uma variável de tipo de dados SDT pré-definido (vem com a KB) Messages(collection).
Quando se executam os métodos: Save, Check, Load, Delete se disparam e carregam as mensagens geradosautomaticamente por GeneXus assim como as regras Msg e Error definidos na transação. Se recomenda quesempre se recupere a lista destas mensagens e se faça uma “gerência de erros”.
As mensagens mais comuns geradas automaticamente por GeneXus são:
As regras Msg e Error aceitam em sua definição além do parâmetro com a mensagem a mostrar, um segundoparâmetro que define o Identificador da mensagem. O objetivo é que quando se execute a transação comoBussiness Component, e se obtenha a lista de mensagens ocorridas depois de executar uma ação sobre a basede dados, se tenha de cada mensagem, além da mensagem em si, seu identificador, sendo possível assimavaliar o identificador da mensagem para codificar o comportamento como conseqüência:
Msg|Error(<mensaje>, <Id do mensaje>)
Exemplos de <Id do mensaje>: "1", "2", "Error1", "Error2" ou uma descrição como ser"CustomerNameCannotBeEmpty", etc.
Se não especificar um identificador para a mensagem,, terá que perguntar pelo texto da mensagem.Nota: Para as mensagens geradas automaticamente por GeneXus, o Id é sempre em Inglês(independentemente do idioma selecionado no modelo).
278
Aqui completaríamos o processo de geração de recibos.
Observemos primeiramente um fato importante: um data provider não somente pode devolver um SDT simplesou coleção, mas também um BC simples ou coleção.
Importante: o BC devolvido somente poderá inserir na base de dados. Isto é, um Data Provider somentepermite satisfazer sua estrutura, para depois fazer uma operação de INSERT sobre a base de dados. Não serápossível fazer uma atualização ou eliminação com um BC carregado via Data Provider.
Observe por outro lado como foi definido a variável &message de tipo de dados Messages.Message,correspondente a os itens do SDT pré-definido Messages devolvido pelo método GetMessages do BC.
Uma vez que obtemos e ingressamos na tabela BILL todos os recibos correspondentes ao faturamento,deveríamos percorrer as faturas consideradas, e modificar o valor de seu atributo InvoicePendingFlag,passando-o para False. Lembremos que o valor deste atributo se utiliza para não processar duas vezes umamesma fatura. Lembremos que este valor se utiliza no Data Provider no cálculo do elemento BillAmount:
sum( InvoiceAmount, InvoiceDate >= &start and InvoiceDate <= &end and InvoicePendingFlag )
Deixaremos pendente esta última parte para quando estudarmos, umas páginas mais adiante, as formas deatualização direta, dentro de procedimentos exclusivamente, dos registros das tabelas.
279
Adiantamos aqui em apresentar outro tipo de objeto interativo que estudaremos um pouco mais adiante, a WebPanel, mostramos um objeto deste tipo em execução. Sua função será pedir ao usuário final um par de valores,que armazenem nas variáveis correspondentes de tipo Date (&startDate e &endDate) e depois, quando ousuário pressione o botão associado ao Evento Enter da Web Panel, se execute seu código.
Observe que o código é idêntico ao do procedimento visto antes. Com este exemplo pretendemos mostrar quemediante um Business Component pode se atualizar a base de dados a partir de qualquer objeto GeneXus.
280
Vamos supor que cada vez que se insere um novo país no sistema, é necessariamente devido a que se temum novo cliente desse país. Vamos supor que como requerimento, além disso, não deve ficar abaixo nenhumacircunstância ingressado um país sem pelo menos um cliente associado.
Tal como temos implementada a aplicação, isto não se está controlando. Como se insere um país e um clientepara o mesmo? O usuário deve executar a transação Country, ingressar o país. Depois abrir a transaçãoCustomer, e ingressar os dados do cliente e confirmar. Mas o que acontece se cair o sistema quando se estáingressando o cliente? Ficará o país ingressado (lembrar que cada transação por default faz commit ao final).
281
Necessitamos que a inserção do país na tabela COUNTRY e a inserção do cliente na tabela CUSTOMER serealizam dentro de uma mesma UTL. Como temos visto, as operações efetuadas por 2 transações não podemser incluídas numa única UTL. Por esta razão, necessitaremos fazer a inserção dentro da mesma transação.
Como? Dentro da transação Country, definimos uma variável &customer, de tipo business componentCustomer (para isso, teremos previamente que ter setado a propriedade Business Component da transaçãoCustomer, de tal maneira que se crie este tipo de dados na KB). Umaa vez efetuado isto, simplesmenteinserindo no form a variável (da mesma forma que acontecia com um SDT), GeneXus colocará controles paracada um dos membros da estrutura: &customer.CustomerId, &customer.CustomerName,&customer.CustomerAddress, etc.
As variáveis por default nas transações são de saída, terá que especificar regra Accept para que possam serescritas em execução e o possa ingressar valores para as mesmas.
O Save do customer deverá realizar-se necessariamente depois de inserir o país em sua tabela (do contrário aintegridade referencial falha). Portanto nas regras:
&customer.Save() on AfterInsert;
Sabemos que neste momento o registro correspondente ao país já terá sido gravado, e depois neste Save... nacontinuação, o commit automático.
282
283
Se existe uma regra que chama um objeto que possui interface, isto é, form, essa regra não é incluída no BC.Existe uma forma de especificar que uma regra declarada na transação não seja aplicada quando se executa atransação, mas somente quando se executa o Business Component associado: é qualificando a regra com[BC].
Exemplo:
[BC] Default( BillDate, &today);
Se quiser qualificar de uma só vez um conjunto de regras:
[BC]
{regla1;
regla2;...
regran;}
O mesmo vale para eventos.
Analogicamente, existem qualificadores para indicar que uma regra somente se execute se estiver correndo atransação com seu form web: [WEB].
284
285
A modificação de dados da base de dados se realiza em forma implícita: não tem um comandoespecífico de atualização.
Para atualizar um ou vários atributos de uma tabela se utiliza o comando For each, e dentro domesmo o comando de atribuição
Podem ser realizadas várias atualizações dentro do mesmo For each, podendo estes pertencer tantoa própria tabela base como a tabela estendida.
O exemplo que apresentamos completa o que havíamos iniciado a respeito ao processo de geraçãode recibos.
Observe que aqui se está percorrendo a tabela INVOICE, filtrando por InvoiceDate, porInvoicePendingFlag, assim como por CustomerId (na listagem de navegação se observa que mesmonão tendo sido especificado order, ao receber no atributo CustomerId, que é foreing key, se ordenapor este atributo para otimizar). Dentro do for each, para cada fatura que cumpre as condições, seatualiza o valor do atributo InvoicePendingFlag. Aqui poderiam atualizar-se não somente esseatributo, mas qualquer da própria tabela INVOICE, ou de sua estendida, com as exceções indicadasna continuação.
286
Vamos supor que temos o seguinte diagrama de Bachman genérico:
E no Source de um procedimento fazemos:
For eachC = &C
E = &ED = &D
Endfor
Aqui a tabela base do For each será claramente a de chave primária A e dentro do For each estamosatualizando tanto atributos da própria tabela base como da estendida. Em que momento ocorreefetivamente a atualização dos registros envolvidos?A atualização não ocorre quando encontra um comando de atribuição dentro do For each, mas simdepois que se encontram todos, para cada instancia da tabela base, isto é,quando se chega aoEndfor para cada iteração.
Como vimos antes, não podemos atualizar dentro do comando For each atributos da chave primária.Todavia poderíamos querer atualizar um atributo que não está definido como chave primária, estádefinido como chave candidata (mediante um índice unique).
Se o atributo é chave candidata, deve controlar-se que não se dupliquem seus valores, caso encontreduplicado, não permite fazer a atualização.Se desejar tomar uma ação no caso disso ocorrer, o comando For each agrega a cláusula whenduplicate. Somente tem sentido se existe alguma chave candidata para esse For each.
��
�
�
�
��
�
�
287
Realizar um “blocking” nas operações de atualização da BD significa armazená-las emmemória e enviá-las em grupo ao DBMS. Ao invés de interagir com o DBMS em cadaoperação de atualização, a interação tem lugar somente cada N operações de atualização,onde N é o número que se estabelece na cláusula Blocking.
Não será o caso de nosso exemplo, mas o que aconteceria se estiver fazendo umaatualização massiva que inclui algum atributo chave candidata da tabela, e se encontramduplicados para alguns dos registros do grupo de 1000 que se está processando?
Nesse caso, uma vez chegado o buffer com as 1000 atualizações, ao enviar ao BD ocomando UPDATE do grupo, saltará o error de duplicados e se iterará sobre o grupo,realizando um comando UPDATE individual de BD, um por um.
288
Para eliminar dados se utiliza o comando Delete dentro do comando For each.
O comando Delete elimina o registro que estiver posicionado no momento dado. É por isso, que nãopode aparecer “solto” dentro do Source. Deve estar dentro de um comando For each, cuja tabelabase seja a tabela que se quer eliminar registros. Somente se eliminam os registros da tabela base,não da estendida.
Sei desejamos eliminar todas as faturas anteriores a uma data informada, podemos programar umprocedimento:
For each
where InvoiceDate <=&dateFor each
defined by InvoiceDetailQuantityDELETE // se eliminam as linhas
EndforDELETE //depois de eliminar as linhas se elimina o cabeçalho
Endfor
Para uma eliminação mais eficiente, dependendo do número de registros da base de dados, convêmagregar cláusula Blocking com um fator de bloqueo, N, adequado. Por exemplo, se agregar “Blocking1000” a eliminação física não é realizada em cada iteração, mas sim a cada 1000 vezes que sechegue ao Endfor, se eliminarão o grupo de 1000 registros em um único acesso a Base de Dados(ao invés de 1000).
289
Vamos supor que queremos implementar um procedimento que faça o seguinte: para o produto cujocódigo é recebido por parâmetro, insere um novo preço (também recebido por parâmetro) em sualista de preços, para a data correspondente ao dia em que se executa o procedimento. Este devecriar um novo registro na tabela PRODUCTPRICELIST, que está composta pelos atributos ProductId,ProductPriceListDate e ProductPriceListPrice, sendo sua chave primária composta, conformada porProductId e ProductPriceListDate.
Para isso se utiliza o comando new que escrevemos acima. Observemos que dentro do mesmoaparecem comandos de atribuição, onde se dá valor aos atributos da tabela que se quer inserir oregistro.
Cada vez que GeneXus encontra um new, deve determinar a tabela na qual se realizará a inserção(tabela base do new). É determinada a partir dos atributos que aparecem dentro do comando new,do lado esquerdo numa atribuição.
O único controle que realiza, é o de duplicados. Em nosso caso, se existir um registro com os valoresde ProductId e ProductPriceListDate, não se realizará a inserção. Se programar a cláusula whenduplicate, se atualiza o registro encontrado.
Observemos que para realizar a atualização do atributo, a atribuição deve estar dentro de umcomando For each. Se não colocamos o For each não se realizará a atualização do preço para esseregistro, isto é, é como se não tivesse incluído cláusula when duplicate.
Se quiser inserir um registro para o qual exista chave candidata duplicada, também se controla e nãose realizará a inserção. Em caso de existir cláusula when duplicate, se executa.
290
O caso mais comum será ter o comando new dentro de um for each, visto quem em geral se querinserir registros na base e cálculos efetuados em função de outros.
O mesmo processo de geração de recibos que havíamos resolvido anteriormente utilizando DataProvider e Business Component, poderíamos realizá-lo com um procedimento que utilize o comandonew de inserção. Qual alternativa você escolheria? Lembre que com os comandos de atualizaçãodentro de procedimentos, o único controle que será realizado é o de duplicados.
Se as inserções são muitas, assim como vimos para o caso das atualizações, dispomos da cláusulablocking para ir guardando em um buffer e depois fazer a inserção de todos os registros uma vez.
Em nosso caso não falhará a inserção, devido que a chave primária é autonumber, e que não temoschaves candidatas (através de índices unique) na tabela de recibos (BILL).
Mas se não for o caso, como as inserções vão sendo realizadas no buffer até chegar a 1000, não épossível até completar o buffer saber se alguma operação falhará. Quando se executa o INSERTmassivo da base de dados, ali se pode falhar alguma inserção. Nesse caso, se itera nos elementosdo buffer, executando o INSERT simples, um por um. Se existe cláusula When duplicate no New,então pelos registros que falhem, se executa a cláusula.
291
Na sintaxe apresentada, bloque_asignaciones1 é um bloque de código composto na sua maioria porsucessivos comandos de atribuição (aqui se atribui valor aos atributos da tabela na que vai inserir oregistro, podem também atribuir-se valores a variáveis).
A cláusula Defined By opcional, se incorpora os mesmos efeitos que no comando For each: ajudar adeterminar a tabela base.
A tabela base do new se obtêm dos atributos do Defined By e os que apareçam do lado esquerdo deatribuições dentro do bloque_asignaciones1. Aqui não se utiliza a tabela estendida.
No caso de que o comando new esteja dentro de uma cláusula repetitiva (ex. For each), é possívelreduzir o número de acessos a base de dados usando a cláusula blocking.
O comando new realiza um controle de duplicados, de tal maneira que não se permite inserir umregistro que já exista na tabela.
A cláusula when duplicate do comando permite programar a ação em caso de que o registro jáexista na tabela base (tanto por chave primária como por chave candidata).
Normalmente, ao ocorrer o que acabamos de falar, se quer atualizar alguns dos atributos de ditoregistro. Para isso, em bloque_asignaciones2 se realizam tais atribuições, mas como o que se faz éatualizar um registro (e não inserir um novo), estas atribuições aparecem entre “For each – Endfor”,como vimos, as atualizações somente podem ser realizadas dentro de um For each.
Se a cláusula when duplicate para um new não for especificada, se o registro que se quer inserir seencontra duplicado não se realizará nenhuma ação e a execução continuará no comando seguinte.Isto é, como não pode inserir o registro porque já existe um, não faz nada e segue adiante com opróximo comando.
292
Nos procedimentos o único controle de integridade que se realiza automaticamente é o controle deduplicados. O controle de integridade referencial fica por conta do programador, o que não ocorre nastransações (em um Business Component).
Fica claro da numeração mostrada acima que os Business Components oferecem todas as garantiase constituirão a forma de atualização privilegiada.
Cabe perguntar-se então: quando atualizar utilizando estes comandos? A resposta é: quando aperformance for um problema.
Um exemplo discutível é o que utilizamos para alterar o valor do atributo InvoicePendingFlag a False.Não havia nenhuma regra que o implicara, não envolvia integridade referencial, nem duplicados epodia envolver milhares de registros. Qual seria a alternativa? Prender a propriedade BusinessComponent da transação Invoice, e no procedimento MarkInvoiceAsNotPending definir variável&invoice com esse tipo de dados e depois:
for eachwhere InvoiceDate >= &startDatewhere InvoiceDate <= &endDatewhere InvoicePendingFlag
&invoice.Load ( InvoiceId)&invoice.InvoicePendingFlag = False&invoice.Save()
endfor
Mas esta solução será mais ineficiente que a atualização direta utilizando “blocking factor”, sabendoque milhares de faturas deverão ser atualizadas.
Recommended