L'aggiunta di un campo calcolato per una query in fase di esecuzione
-
22-09-2019 - |
Domanda
sto ottenendo i dati utilizzando una query a Delfi, e vorrei aggiungere un campo calcolato alla query prima che corre. Il campo calcolato sta usando i valori nel codice così come la query, quindi non posso solo calcolare in SQL.
So di poter collegare un evento OnCalcFields
per rendere realtà il calcolo, ma il problema è dopo l'aggiunta del campo calcolato non ci sono altri campi nella query ...
Ho fatto qualche ricerca e ha scoperto che tutti i defs campo sono creati ma i campi effettivi sono creati solo
if DefaultFields then
CreateFields
I campi di default è specificato
procedure TDataSet.DoInternalOpen;
begin
FDefaultFields := FieldCount = 0;
...
end;
Il che indicherebbe che se si aggiungono i campi che si ottiene solo i campi aggiunti.
Vorrei tutti i campi nella query così come il quelli che ho aggiungere.
Questo è possibile o devo aggiungere tutti i campi che sto usando come bene?
Soluzione
Delphi ha ora la possibilità di combinare i campi generati automatici e campi calcolati: Data.DB.TFieldOptions.AutoCreateMode un'enumerazione di tipo TFieldsAutoCreationMode . In questo modo è possibile aggiungere i campi calcolati in fase di esecuzione. Francois ha scritto nella sua risposta come aggiungere un campo in fase di esecuzione.
Diverse modalità di TFieldsAutoCreationMode:
-
acExclusive
Quando non ci sono campi persistenti a tutti, allora si creano campi automatici. Questa è la modalità predefinita.
-
acCombineComputed
I campi automatici vengono creati quando l'insieme di dati non ha campi persistenti o ci sono solo campi persistenti calcolati. Questo è un modo conveniente per creare campi calcolati persistenti in fase di progettazione e di lasciare che il set di dati di creare campi di dati automatici.
-
acCombineAlways
campi automatici per i campi del database verranno creati quando non ci sono campi persistenti.
Altri suggerimenti
Nulla vi impedisce di creare tutti i campi prima nel codice,
quindi aggiungere i campi calcolati.
È possibile utilizzare un "tipo violato", per usare le CreateFields protette:
type
THackQuery = class(TADOQuery)
end;
[...]
MyQuery.FieldDefs.Update;
THackQuery(MyQuery).CreateFields;
o prendere in prestito qualche codice 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]);
quindi creare i campi calcolati:
MyQueryMyField := TStringField.Create(MyQuery);
with MyQueryMyField do
begin
Name := 'MyQueryMyField';
FieldKind := fkCalculated;
FieldName := 'MyField';
Size := 10;
DataSet := MyQuery;
end;
È necessario aggiungere tutti i campi in aggiunta al campo calcolato.
Una volta che si aggiunge un campo, è necessario aggiungere tutti i campi che si desidera nel set di dati.
Delphi chiama questo persistenti campi contro campi dinamici. Tutti i campi sono o persistente o dinamico. Purtroppo, non si può avere una miscela di entrambi.
Un'altra cosa da notare, dalla documentazione è
campi persistenti liste di componenti sono memorizzati nell'applicazione, e non cambia anche se la struttura di un database sottostante un set di dati è cambiato.
Quindi, fate attenzione, se in seguito si aggiungono ulteriori campi a una tabella, è necessario aggiungere i nuovi campi al componente. Stessa cosa con l'eliminazione di campi.
Se davvero non si vuole campi persistenti, c'è un'altra soluzione. Su qualsiasi griglia o di controllo che dovrebbe mostrare il campo calcolato, è possibile disegnare su misura. Ad esempio, molti controlli griglia hanno un evento OnCustomDraw. Si può fare il calcolo lì.
Se si è conoscere i propri campi da calcolare nomi in fase di esecuzione, è possibile utilizzare una cosa del genere.
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;