Adicionando um campo calculado a uma consulta no tempo de execução
-
22-09-2019 - |
Pergunta
Estou recebendo dados usando uma consulta em Delphi e gostaria de adicionar um campo calculado à consulta antes de executar. O campo calculado está usando valores no código e na consulta, para que eu não possa simplesmente calculá -lo no SQL.
Eu sei que posso anexar um OnCalcFields
Evento para realmente fazer o cálculo, mas o problema é depois de adicionar o campo calculado, não há outros campos na consulta ...
Eu fiz algumas escavações e descobri que todos os defs de campo são criados, mas os campos reais são criados apenas
if DefaultFields then
CreateFields
Campos padrão são especificados
procedure TDataSet.DoInternalOpen;
begin
FDefaultFields := FieldCount = 0;
...
end;
O que indicaria que, se você adicionar campos, você só obtém os campos que adicionou.
Eu gostaria de todos os campos da consulta, bem como os que eu acrescento.
Isso é possível ou tenho que adicionar todos os campos que estou usando também?
Solução
A Delphi agora tem a opção de combinar campos gerados automáticos e campos calculados: Data.db.tfieldOptions.autocreatemode Uma enumeração do tipo TfieldsAutocrationMode. Dessa forma, você pode adicionar seus campos calculados em tempo de execução. François escreveu em sua resposta como adicionar um campo em tempo de execução.
Diferentes modos de tfieldsautocrationMode:
Acexclusivo
Quando não há campos persistentes, os campos automáticos são criados. Este é o modo padrão.
acumpinecomputado
Os campos automáticos são criados quando o conjunto de dados não tem campos persistentes ou existem apenas campos persistentes calculados. Essa é uma maneira conveniente de criar os campos calculados persistentes no momento do design e permitir que o conjunto de dados crie campos de dados automáticos.
acumulações acumuladas
Os campos automáticos para os campos do banco de dados serão criados quando não houver campos persistentes.
Outras dicas
Nada o impede de criar todos os campos primeiro em seu código,
Em seguida, adicione seus campos calculados.
Você pode usar um "tipo hackeado" para usar os createfields protegidos:
type
THackQuery = class(TADOQuery)
end;
[...]
MyQuery.FieldDefs.Update;
THackQuery(MyQuery).CreateFields;
ou emprestando algum código da createfields:
MyQuery.FieldDefs.Update;
// create all defaults fields
for I := 0 to MyQuery.FieldDefList.Count - 1 do
with MyQuery.FieldDefList[I] do
if (DataType <> ftUnknown) and not (DataType in ObjectFieldTypes) and
not ((faHiddenCol in Attributes) and not MyQuery.FIeldDefs.HiddenFields) then
CreateField(Self, nil, MyQuery.FieldDefList.Strings[I]);
Em seguida, crie seus campos calculados:
MyQueryMyField := TStringField.Create(MyQuery);
with MyQueryMyField do
begin
Name := 'MyQueryMyField';
FieldKind := fkCalculated;
FieldName := 'MyField';
Size := 10;
DataSet := MyQuery;
end;
Você precisa adicionar todos os campos, além do seu campo calculado.
Depois de adicionar um campo, você deve adicionar todos os campos que deseja no conjunto de dados.
Delphi chama esses campos persistentes versus campos dinâmicos. Todos os campos são persistentes ou dinâmicos. Infelizmente, você não pode ter uma mistura de ambos.
Outra coisa a observar, da documentação é
As listas de componentes de campos persistentes são armazenadas em seu aplicativo e não alteram, mesmo que a estrutura de um banco de dados subjacente a um conjunto de dados seja alterada.
Portanto, tenha cuidado, se você adicionar mais tarde campos adicionais a uma tabela, precisará adicionar os novos campos ao componente. A mesma coisa com os campos de exclusão.
Se você realmente não quer campos persistentes, há outra solução. Em qualquer grade ou controle que deve mostrar o campo calculado, você pode desenhá -lo personalizado. Por exemplo, muitos controles de grade têm um evento OnCustomDraw. Você pode fazer seu cálculo lá.
Se você sabe que o seu nome de campos calculado em tempo de execução, pode usar algo assim.
var
initing:boolean;
procedure TSampleForm.dsSampleAfterOpen(
DataSet: TDataSet);
var
i:integer;
dmp:tfield;
begin
if not initing then
try
initing:=true;
dataset.active:=false;
dataset.FieldDefs.Update;
for i:=0 to dataset.FieldDefs.Count-1 do
begin
dmp:=DataSet.FieldDefs.Items[i].FieldClass.Create(self);
dmp.FieldName:=DataSet.FieldDefs.Items[i].DisplayName;
dmp.DataSet:=dataset;
if (dmp.fieldname='txtState') or (dmp.FieldName='txtOldState') then
begin
dmp.Calculated:=true;
dmp.DisplayWidth:=255;
dmp.size:=255;
end;
end;
dataset.active:=true;
finally
initing:=false;
end;
end;
procedure TSampleForm.dsSampleAfterClose(
DataSet: TDataSet);
var
i:integer;
dmp:TField;
begin
if not initing then
begin
for i:=DataSet.FieldCount-1 downto 0 do
begin
dmp:=pointer(DataSet.Fields.Fields[i]);
DataSet.Fields.Fields[i].DataSet:=nil;
freeandnil(dmp);
end;
DataSet.FieldDefs.Clear;
end;
end;
procedure TSampleForm.dsSampleCalcFields(
DataSet: TDataSet);
var
tmpdurum,tmpOldDurum:integer;
begin
if not initing then
begin
tmpDurum := dataset.FieldByName( 'state' ).AsInteger;
tmpOldDurum:= dataset.FieldByName( 'oldstate' ).AsInteger;
dataset.FieldByName( 'txtState' ).AsString := State2Text(tmpDurum);
dataset.FieldByName( 'txtOldState' ).AsString := State2Text(tmpOldDurum);
end;
end;
procedure TSampleForm.btnOpenClick(Sender: TObject);
begin
if dsSample.Active then
dsSample.Close;
dsSample.SQL.text:='select id,state,oldstate,"" as txtState,"" as txtOldState from states where active=1';
dsSample.Open;
end;