Hinzufügen ein berechnetes Feld zu einer Abfrage zur Laufzeit
-
22-09-2019 - |
Frage
Ich erhalte Daten eine Abfrage in Delphi verwenden und möchte ein berechnetes Feld, um die Abfrage hinzuzufügen, bevor es ausgeführt wird. Das berechnete Feld wird auch Werte in Code verwenden als die Abfrage, so kann ich nicht einfach in SQL berechnen.
Ich weiß, ich kann ein OnCalcFields
Ereignis anhängen, um tatsächlich die Berechnung zu machen, aber das Problem ist, nachdem das berechnete Feld Hinzufügen es keine anderen Felder in der Abfrage sind ...
habe ich einige graben und fand, dass alle Feld defs erstellt werden, sondern die tatsächlichen Felder werden nur erstellt
if DefaultFields then
CreateFields
Standard Felder angegeben
procedure TDataSet.DoInternalOpen;
begin
FDefaultFields := FieldCount = 0;
...
end;
, die anzeigen würde, dass, wenn Sie Felder hinzufügen nur die Felder erhalten Sie hinzugefügt.
Ich möchte alle die Felder in der Abfrage als auch die, die ich hinzufügen.
Ist das möglich oder muss ich fügen Sie alle Felder, die ich auch bin mit?
Lösung
Delphi hat nun die Möglichkeit, automatisch generierte Felder zu kombinieren und berechnete Felder: Data.DB.TFieldOptions.AutoCreateMode eine Aufzählung von Typ TFieldsAutoCreationMode . Auf diese Weise können Sie Ihre berechneten Felder zur Laufzeit hinzufügen. Francois schrieb in seiner Antwort, wie ein Feld zur Laufzeit hinzuzufügen.
Verschiedene Modi von TFieldsAutoCreationMode:
-
acExclusive
Wenn es keine persistenten Felder überhaupt, dann sind automatische Felder erstellt. Dies ist der Standardmodus.
-
acCombineComputed
Die automatischen Felder werden erstellt, wenn der Datensatz keine persistenten Felder hat oder es werden nur persistente Felder berechnet. Dies ist eine bequeme Art und Weise die persistenten berechneten Felder zur Entwurfszeit zu erstellen und lassen Sie die Datenmenge automatische Datenfelder erstellen.
-
acCombineAlways
Automatische Felder für die Datenbankfelder erstellt werden, wenn es keine persistenten Felder sind.
Andere Tipps
Nichts hindert Sie von der Erstellung alle Felder zunächst in Ihrem Code,
dann berechnete Felder hinzufügen.
Sie können entweder einen „gehackten Typ“ verwenden, um die geschützten Create zu verwenden:
type
THackQuery = class(TADOQuery)
end;
[...]
MyQuery.FieldDefs.Update;
THackQuery(MyQuery).CreateFields;
oder leihen einige Code von Create:
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]);
dann erstellen Sie Ihre Felder berechnet:
MyQueryMyField := TStringField.Create(MyQuery);
with MyQueryMyField do
begin
Name := 'MyQueryMyField';
FieldKind := fkCalculated;
FieldName := 'MyField';
Size := 10;
DataSet := MyQuery;
end;
Sie müssen alle Felder zusätzlich zu Ihrem berechneten Feld hinzuzufügen.
Wenn Sie ein Feld hinzufügen, haben Sie alle Felder hinzuzufügen, dass Sie in dem Datensatz werden sollen.
Delphi ruft diese persistenten Felder und dynamische Felder aus. Alle Felder sind entweder persistent oder dynamisch. Leider kann man nicht eine Mischung aus beidem hat.
Eine andere Sache zu beachten, von der Dokumentation ist
Persistent Felder Komponentenlisten sind in Ihrer Anwendung gespeichert ist, und dies nicht tun Veränderung, auch wenn die Struktur eines Datenbank zugrunde liegenden ein Datensatz ist geändert.
Also, seien Sie vorsichtig, wenn Sie später weitere Felder hinzufügen zu einer Tabelle, müssen Sie die neuen Felder der Komponente hinzuzufügen. Das Gleiche gilt für Felder zu löschen.
Wenn Sie wirklich nicht persistente Felder wollen, ist es eine andere Lösung. An jedem Gitter oder Steuerelement, das das berechnete Feld zeigen soll, können Sie benutzerdefinierte es zeichnen. Zum Beispiel haben viele Grid Controls ein OnCustomDraw Ereignis. Sie können dort Ihre Berechnung tun.
Wenn Sie haben, zu wissen, zur Laufzeit berechneten Felder Namen zu sein, können Sie so etwas verwenden.
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;