|
Usuários |
|
78 Usuários Online
|
|
[Artigos]
Queries dinâmicas no servidor DataSnap |
Publicado por rboaro : Quarta, Janeiro 23, 2013 - 07:44 GMT-3 (2547 leituras)
7 Comentários Enviar para um amigo Versão para impressão
|
Há muitos motivos para evitar o uso do commandText do clientDataSet , entre eles podemos destacar alguns pontos.
* A Descentralização de acesso a dados e de negócio abre um impácto negativo grande na manutenção desses aplicativos clientes.
* Dificuldade na reutilização de codigo
* Fragilidade a SQL Injection devido a Sql trafegando na Rede
* Dependência do banco e de seus recursos, o que causa pouca escalabilidade.
Abaixo transcrevo uma técnica de criar um SQL no servidor usando métodos presentes no CDS(Cliente) -> DSP(Servidor), este configurando seu proprio DataSet.
Quando eu digo que lugar de SQL é no servidor é porque vejo muitos programadores Delphi usando DataSnap como um simples middleware de conectividade com o banco de dados, não como uma verdadeira camada de “regras de negócio” que proporcione abstração de dados do lado cliente.
Uma dificuldade comum, que muitos consideram justificativa para usar a famigerada opção poAllowCommandText no TDataSetProvider, é fazer queries customizadas na aplicação cliente que não tenham um conjunto definido de parâmetros.
Como fazer uma pesquisa em um cadastro de clientes, por exemplo, por Cidade e/ou Nome e/ou Data de Cadastro? Só esses três parâmetros permitem 8 combinações de consulta (23). Uma solução seria escrever 8 queries diferentes no servidor e mais 8 dataset providers. Uma trabalheira danada. Já se fossem 5 campos de pesquisa para o usuário, seriam 32 combinações! Por aí que muita gente escolhe construir dinamicamente a query no cliente e usar a propriedade CommandText de TClientDataSet.
Uma alternativa é escrever uma query em que a cláusula WHERE contemple parâmetros nulos. Por exemplo:
WHERE
((:Cidade IS NOT NULL) AND (Cidade = :Bairro)) OR
((:Cadastro IS NOT NULL) AND (Cadastro >= :Cadastro))
O problema com essa técnica é que a performance da query fica muito comprometida. Não há como o otimizador do banco de dados fazer um plano de execução eficiente.
A minha solução preferida é simplesmente mandar os valores dos parâmetros para o TDataSetProvider montar a query dinamicamente. Falando assim parece simples, mas… na verdade é simples!
A chave é usar o evento BeforeGetRecords, presente tanto no TClientDataSet (no lado cliente), quanto no TDataSetProvider (no lado servidor). O evento tem um parâmetro OwnerData do tipo OleVariant (para todos efeitos, idêntico a um Variant) que pode ser atribuido no cliente e depois lido no servidor. Construí uma pequena aplicação de exemplo que lê a tabela Country do banco de dados Access de demonstração que fica em “…\Borland Shared\Data\dbdemos.mdb”. Veja abaixo uma imagem da aplicação filtrando os campos “Continent”, “Population” (valo mínimo) e “Name” (iniciais).
O que eu fiz no cliente foi apenas montar um array Variant com os parâmetros definidos pelo usuário e atribuí-lo a OwnerData. Não mexi com a propriedade Params do ClientDataSet simplesmente porque não existe nenhum parâmetro no dataset: o SQL será montado no servidor dinamicamente.
procedure TForm1.cdsCountryBeforeGetRecords(
Sender: TObject; var OwnerData: olevariant);
var
ContinentEqual: string;
PopulationMin: integer;
NameStart: string;
begin
ContinentEqual := Trim(Edit1.Text);
PopulationMin := StrToIntDef(Trim(Edit2.Text), 0);
NameStart := Trim(Edit3.Text);
OwnerData := VarArrayOf([ContinentEqual, PopulationMin, NameStart]);
end;
Com isso o cliente só precisa deixar o usuário entrar com os parâmetros que quiser na tela e dar Open no dataset (não esqueça de dar um Close antes de fazer uma nova pesquisa). O servidor se encarregará de montar o código SQL apropriado com os parâmetros que não forem vazios.
O mesmo evento BeforeGetRecords é usado no servidor de aplicação, mas agora usamos o do componente TDataSetProvider para alterar em código a propriedade SQL do objeto de query (no caso, um TADOQuery):
procedure TRemoteDataModule1.dspCountryBeforeGetRecords(
Sender: TObject; var OwnerData: olevariant);
var
ContinentEqual: string;
PopulationMin: integer;
NameStart: string;
SQLWhere: string;
procedure AddWhere(const S: string);
begin
if SQLWhere = ” then
SQLWhere := ‘WHERE’
else
SQLWhere := SQLWhere + ‘ AND’;
SQLWhere := SQLWhere + ‘ (‘ + S + ‘)’;
end;
begin
if VarIsArray(OwnerData) and (VarArrayHighBound(OwnerData, 1) = 2) then
begin
ContinentEqual := OwnerData[0];
PopulationMin := OwnerData[1];
NameStart := Ownerdata[2];
SQLWhere := ”;
if ContinentEqual = ” then
AddWhere(Format(‘Continent = ”%s”’, [ContinentEqual]));
if Populationmin &;gt; 0 then
AddWhere(Format(‘Population >= %d’, [PopulationMin]));
if NameStart = ” then
AddWhere(Format(‘Name LIKE ”%s%%”’, [NameStart]));
qryCountry.SQL.Clear;
qryCountry.SQL.Add(‘SELECT * FROM Country’);
qryCountry.SQL.Add(SQLWhere);
end;
end;
Veja abaixo o resultado de uma query mais específica, que define o continente “South America” e uma quantidade mínima de 100.000.000 habitantes. Com essa técnica a aplicação cliente fica totalmente independente da estrutura do banco de dados. Se mudar o banco de dados, ou mesmo a estrutura das tabelas, nada tem que ser alterado no cliente! O simples fato de isolar a camada de apresentação do acesso a dados já é uma vantagem na administração do projeto. Repitam comigo: CommandText nunca mais!
Link Original do Artigo:
http://marcosalles.wordpress.com/2011/01/23/queries-dinamicas-no-servidor-datasnap/
|
|
Comentários | |
| | Comentários pertencem aos seus respectivos autores. Não somos responsáveis pelo seus conteúdos. |
[ Comentários não permitidos para usuários anônimos. Por gentileza, registre-se ou conecte-se ao sistema
por: deophanes (deophanes@click21.com.br) : Jan 29, 2013 - 02:07 (Informações sobre o membro | Enviar uma mensagem) http:// | Esse modelo eu já usava com o Delphi 2006 onde eu tinha uma form padrao e usava herança visual, nessa tela ficava o clientdataset e o Datasouce, daí dava para passar os valores dos edits, mas agora estou usando o Delphi 2010 e não consegui fazer essa mesma estrutura de herança visual, com o clientdataset no form, tive que colocar em um Datamodule, ficando apenas o Datasouce, daí a minha dúvida de como usar essa consulta. Seria fazendo algum cast neste Datasouce padrao do form?
espero ter sido mais claro. | [ Comentários não permitidos para usuários anônimos. Por gentileza, registre-se ou conecte-se ao sistema
por: marcosalles (salhamoda@uol.com.br) : Jan 29, 2013 - 05:46 (Informações sobre o membro | Enviar uma mensagem) http://marcosalles.wordpress.com/ | Entendi amigo ..Existem algumas formas de obter isto. Não estou dizendo que a maneira mais correta é a que vou sugerir. Acho ate que vc deve se manifestar sobre esta situação no forum , pq a meu ver foi uma pergunta pertinente e interressante .
Um dos meios é atribuir este evento a um Form (prefiro uma classe genérica) , mas para resolver inicialemente vamos imaginar no Form (Ok)
Na secção Private (Prefiro uma Classe Generica)do form escreva um evento com os mesmo atributos do Evento BeforeGetRecords ... Ligue o evento do Objeto clientdataSet a este Manipulador no Onshow ou no Oncreate do Form . Tipo isto
TCleintDataSet(SeuDataSource.DataSet)..BeforeGetRecords:= O Manipulador_Que_Vc_Definiu;
dai para lá tudo deve funcionar como antes
ps) Não testei e nen estou dizendo ser a melhor forma
obs) Presto Consultoria e desenvolvimento nesta àrea. Boa Sorte
[]sds | [ Comentários não permitidos para usuários anônimos. Por gentileza, registre-se ou conecte-se ao sistema
[ Comentários não permitidos para usuários anônimos. Por gentileza, registre-se ou conecte-se ao sistema
|
|
Edição 112 |
|
|
50 Programas Fontes |
|
|
Produtos |
|
|