Добавление вычисляемого поля в запрос во время выполнения
-
22-09-2019 - |
Вопрос
Я получаю данные с помощью запроса в Delphi и хотел бы добавить вычисляемое поле в запрос перед его запуском.Вычисляемое поле использует значения как в коде, так и в запросе, поэтому я не могу просто вычислить его в SQL.
Я знаю, что могу прикрепить OnCalcFields
Событие для фактического выполнения расчета, но проблема в том, что после добавления вычисляемого поля в запросе нет других полей...
Я немного покопался и обнаружил, что все определения полей создаются, но фактические поля создаются только
if DefaultFields then
CreateFields
Поля по умолчанию указаны
procedure TDataSet.DoInternalOpen;
begin
FDefaultFields := FieldCount = 0;
...
end;
Это означает, что если вы добавите поля, вы получите только те поля, которые вы добавили.
Мне нужны все поля в запросе, ТАКЖЕ, КАК те, которые я добавляю.
Возможно ли это, или мне нужно добавить все поля, которые я использую?
Решение
В 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;