Добавление вычисляемого поля в запрос во время выполнения
Я получаю данные, используя запрос в Delphi, и хотел бы добавить вычисляемое поле к запросу до его запуска. В вычисляемом поле используются значения как в коде, так и в запросе, поэтому я не могу просто вычислить его в SQL.
Я знаю, что могу прикрепить OnCalcFields
Событие для фактического расчета, но проблема в том, что после добавления вычисляемого поля в запросе нет других полей...
Я немного покопался и обнаружил, что все определения полей созданы, но фактические поля созданы только
if DefaultFields then
CreateFields
По умолчанию поля указаны
procedure TDataSet.DoInternalOpen;
begin
FDefaultFields := FieldCount = 0;
...
end;
Что означало бы, что если вы добавите поля, вы получите только те поля, которые вы добавили.
Я хотел бы, чтобы все поля в запросе, как хорошо, как те, которые я добавляю.
Это возможно или мне нужно добавить все поля, которые я использую?
5 ответов
Delphi теперь имеет возможность комбинировать автоматически сгенерированные поля и вычисляемые поля: Data.DB.TFieldOptions.AutoCreateMode перечисление типа TFieldsAutoCreationMode. Таким образом, вы можете добавить свои вычисляемые поля во время выполнения. Франсуа написал в своем ответе, как добавить поле во время выполнения.
Различные режимы TFieldsAutoCreationMode:
acExclusive
Если нет постоянных полей вообще, автоматически создаются поля. Это режим "по умолчанию".
acCombineComputed
Автоматические поля создаются, когда в наборе данных нет постоянных полей или имеются только вычисленные постоянные поля. Это удобный способ создания постоянных вычисляемых полей во время разработки и возможность набора данных автоматически создавать поля данных.
acCombineAlways
Автоматические поля для полей базы данных будут созданы, когда нет постоянных полей.
Ничто не мешает вам сначала создать все поля в вашем коде,
затем добавьте свои рассчитанные поля.
Вы можете использовать "взломанный тип", чтобы использовать защищенные CreateFields:
type
THackQuery = class(TADOQuery)
end;
[...]
MyQuery.FieldDefs.Update;
THackQuery(MyQuery).CreateFields;
или заимствовать некоторый код из 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]);
затем создайте свои рассчитанные поля:
MyQueryMyField := TStringField.Create(MyQuery);
with MyQueryMyField do
begin
Name := 'MyQueryMyField';
FieldKind := fkCalculated;
FieldName := 'MyField';
Size := 10;
DataSet := MyQuery;
end;
Вам необходимо добавить все поля в дополнение к вычисляемому полю.
Как только вы добавите поле, вы должны добавить все поля, которые вы хотите в наборе данных.
Delphi называет это постоянными полями по сравнению с динамическими полями. Все поля являются постоянными или динамическими. К сожалению, вы не можете иметь смесь обоих.
Еще одна вещь, которую стоит отметить, из документации
Списки компонентов постоянных полей хранятся в вашем приложении и не изменяются даже при изменении структуры базы данных, лежащей в основе набора данных.
Поэтому будьте осторожны, если позже вы добавите дополнительные поля в таблицу, вам нужно будет добавить новые поля в компонент. То же самое с удалением полей.
Если вы действительно не хотите постоянных полей, есть другое решение. На любой сетке или элементе управления, которые должны показывать вычисляемое поле, вы можете сделать это на заказ Например, многие элементы управления сеткой имеют событие OnCustomDraw. Вы можете сделать свой расчет там.
Если вы знаете, какие имена полей должны быть вычислены во время выполнения, вы можете использовать что-то подобное.
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;
//assume we have an fdquery named FDQuery1
//we write this code in AfterOpen section
Примечание. У этого метода есть проблема. Вычисляемое поле помещается вместо последнего поля. Чтобы решить эту проблему, достаточно добавить в запрос дополнительное поле, например, считать нулевое значение последним полем, например:
select * , 0 as myfield from mytable
procedure Tform1.FDQuery1AfterOpen(DataSet: TDataSet);
var
i : Integer;
aField : TField;
begin
FDQuery1.Close;
for i := 0 to FDQuery1.FieldDefs.Count - 1 do begin
aField := FDQuery1.FieldDefs [ i ].CreateField ( fdQuery1 );
end;
aField.DataSet := fdQuery1;
with aField do
begin
Name := 'myField';
FieldKind := fkCalculated;
FieldName := 'myField';
Size := 10;
DataSet := FDQuery1;
end;
fdQuery1.FieldDefs.Update;
fdQuery1.AfterOpen:=nil;
fdQuery1.Open;
ShowMessage(FDQuery1.FieldByname('myField').AsString);
end;
теперь вы можете написать метод onClacField для myField