De que se trata o artigo

Este artigo trata dos tradicionais lookups ou lookupfields, seus problemas de performance, vantagens e desvantagens e sugere um modo diferente de se substituir lookups ainda mantendo suas facilidades.


Para que serve

Lookups servem para se procurar em uma tabela, através de algum atributo conhecido, a chave primária de um registro para que seja incluída em outra tabela. Serve para escolher o “mestre” do “detalhe” ou as duas “partes” de uma entidade relacional. Por exemplo, pode ser usado em uma operação de venda tanto para selecionar o cliente ou a forma de pagamento, também como para selecionar os produtos.


Em que situação o tema é útil

Um lookup é útil sempre que surge a necessidade de se gravar um campo do tipo chave estrangeira em um registro de uma tabela, sendo essa chave estrangeira uma chave primária em outra tabela, sem ter a necessidade de saber de antemão qual é esta chave, mas sim visualizando outros atributos importantes da tabela relacionada. Quando essa tarefa se torna lenta, sua utilização precisa ser revista.

Lookup Personalizado

A verdade é que não é uma boa prática usar os tradicionais lookups e seus derivados. Normalmente eles são usados desde meados do Delphi 1 e BDE, e devem ser associados com um Dataset (Table, Query, qualquer um) que traga os dados da tabela a ser relacionada. O maior problema é que geralmente se traz todos os dados. A lentidão disso é proporcional à quantidade de lookups em um formulário, o número de tabelas que eles abrem e o tamanho dessas tabelas. Esse artigo mostra como criar um componente reutilizável que cria um lookup “inteligente” que trará os resultados do banco de dados apenas depois de algumas letras inseridas e que manterá um “cache" para consultas futuras. Ele mostrará vários campos e poderá fazer consultas do tipo “like” também.

Em RAD (Rapid Application Development), o tipo de projeto mais comumente desenvolvido em Delphi, VB ou C#, é muito comum o uso de lookups. Uma característica da programação RAD é que, da maneira como tradicionalmente os programadores fazem, ela é bem “procedural” e orientada a eventos. Além disso o foco, ou a direção do desenvolvimento é altamente dependente e inspirada em tabelas. Nesse ambiente, sempre que é necessário cadastrar um registro em uma tabela, por exemplo produtos, e necessita-se de uma chave primária de outra tabela, por exemplo família ou gênero, o programador tem três opções:

• Colocar um lookup para abrir a tabela a ser relacionada, mostrar os registros e trazer a chave primária do registro selecionado no registro que está sendo editado;

• Abrir um formulário de consulta, geralmente “inteligente” que fará perguntas para o usuário preencher campos e com isso montar em tempo de execução uma consulta que traga o mínimo de dados possível do banco, sendo assim bem mais performático;

• Subentender que o usuário já conhece de cor o código da família ou gênero e deixar que ele preencha manualmente, ou consulte primeiro depois preencha, fatalmente incorrendo em erro humano e em violação de integridade referencial caso essa regra não tenha sido imposta no próprio banco.

Por incrível que pareça existem grandes empresas, com ERPs grandes, conceituados e caros que usam a abordagem 3.

Não é intenção do artigo condenar o uso dos lookups tradicionais. Eles têm o seu lugar:

• Protótipos de programas, apresentações para clientes;

• Bases de dados com pouquíssimos valores, e valores fixos, como estados e ICMS;

• Programas em início de desenvolvimento;

• Programadores em início de carreira.

Os três grandes defeitos do lookup, não se restringindo apenas a esses três, são os seguintes:

• Só funcionam com seu dataset aberto, perdendo seus dados quando este está fechado;

• Com um número muito grande de registros eles ficam com uma superfície de seleção (a janela ou aba que se abre clicando-se na seta) muito grande, dificultando a vida do usuário;

• Geralmente não é possível impor filtros nos dados que preenchem o lookup, apenas definir quais colunas serão trazidas. As “linhas”, ou registros da tabela, vêm todas;

Nota: Como os lookups geralmente necessitam de duas colunas, a chave primária e outra que seja humanamente possível de se identificar um registro, não há necessidade de se popular os datasets dos lookups com “select * from tabela”, e este é uma das piores práticas de todas no desenvolvimento de sistemas, e é um dos principais motivos de lentidão, visto que todos os campos são trazidos.

Esses três problemas fazem com que o lookup se torne inconveniente e lento. A lentidão passa a ser cada vez mais perceptível com o crescimento do tamanho das tabelas no banco de dados. Se tornando pior ainda quando o servidor de banco de dados está em um servidor numa rede local e o cliente em outro computador desta mesma rede. Pior ainda se o banco de dados estiver hospedado em um serviço de hosting qualquer, o uso de lookups neste caso é impraticável.

Com certeza há saídas para este problema. Uma delas é usar clientdatasets como uma espécie de cache, que serão populados na inicialização do sistema e não mais se conectarão ao banco de dados. É a prática mais comum. Outra abordagem é colocar ComboBoxes comuns, não-lookups ou não-data-awares, e popular essas caixas de listagem com os valores na inicialização do sistema. Ambas as práticas podem tornar o sistema lento na inicialização.

Outro problema de carregar os dados na inicialização é que caso novos registros sejam adicionados a essas tabelas por outros usuários, em outras estações, os dados não serão atualizados nesses datasets que já estão abertos e populados servindo de cache. Seria necessário implementar um botão de “refresh” ou fechar e abrir novamente o sistema. Nenhuma das duas opções parece cômoda para o usuário.

O meio termo é criar um formulário genérico de consulta que possa ser configurado para consultar qualquer tabela, trazendo os resultados da consulta em uma lista ou grid e devolvendo ao seu “chamador” o código da chave primária do registro selecionado. Esta é a técnica que muitos programadores usam. Mas cada vez que o formulário é chamado a consulta é refeita.

O ideal seria manter um cache de acordo com o que foi buscado, e refazer a consulta no banco de dados apenas se a busca for diferente. Essa regra impõe outra: o usuário deve digitar um mínimo de caracteres (um pedaço do nome, CPF ou família de produtos) para que a consulta seja feita e possa funcionar como um “autocompletar”.

Para tirar pleno proveito da vantagem do RAD não seria conveniente ter que instanciar e destruir esse formulário de consulta (fazendo ainda as devidas conexões) toda vez que for necessário obter um valor de uma tabela mestre para popular uma chave estrangeira. Por isso o inteiro formulário de consulta pode ser encapsulado dentro de um componente. Se esse componente for um Edit com um botão já dispensa a tarefa de adicionar um Edit e um botão de consulta (ou um lookup) para cada campo em um formulário que seja uma chave estrangeira.

Nota do DevMan

Quando um sistema é desenvolvido puramente orientado a objetos e seguindo todos os padrões de projeto e boas práticas dificilmente usa-se lookups. São usadas classes das camadas de persistência para trazer algum objeto procurado, e este é apresentado usando-se uma GUI (form) que seja ligada a ele através de um MVC, por exemplo. Não se fala de chave primária e chave estrangeira nesse contexto, mas o objeto encontrado e trazido (por exemplo a família para o produto) é relacionado com o objeto que está sendo criado ou modificado (o produto).

Nota do DevMan

RAD é um modelo de desenvolvimento de software maravilhoso, mas tem suas limitações. Sem o devido cuidado o código pode virar uma bagunça, com regras de negócio e acesso a dados espalhados em eventos de controle da interface com o usuário. RAD é recomendado em projetos pequenos, rápidos ou em protótipos. Não é objetivo deste artigo criticar o modelo RAD e muito menos a POO, mas é necessário deixar claro que as técnicas apresentadas neste artigo se aplicam muito mais a RAD, e violam vários dos preceitos ou boas práticas da POO. POO é usada e muito bem vinda tanto na construção de um lookup como de qualquer outro componente. São usados conceitos de herança, polimorfismo, associação, o acesso a dados é delegado para uma classe responsável por isso (o dataset), mas mesmo assim lookups são componentes comumente usados em RAD. Quem usa POO a fundo com padrões de projeto e boas práticas escolhe outras maneiras de relacionar seus objetos e representar essa relação na GUI.

Então esse componente terá os seguintes ingredientes:

• Um TButton ou TBitbtn

• Um TEdit

• Um TForm

...

Quer ler esse conteúdo completo? Tenha acesso completo