Ajout d'un champ calculé à une requête au moment de l'exécution
-
22-09-2019 - |
Question
Je reçois des données à l'aide d'une requête dans Delphi, et je voudrais ajouter un champ calculé à la requête avant d'exécuter. Le champ calculé utilise des valeurs dans le code ainsi que la requête calculer je ne peux pas juste dans SQL.
Je sais que je peux joindre un événement OnCalcFields
pour réellement faire le calcul, mais le problème est après l'ajout du champ calculé, il n'y a pas d'autres champs dans la requête ...
Je l'ai fait quelques recherches et a constaté que tous les defs sur le terrain sont créés, mais les champs réels ne sont créés
if DefaultFields then
CreateFields
Par défaut Les champs est spécifié
procedure TDataSet.DoInternalOpen;
begin
FDefaultFields := FieldCount = 0;
...
end;
Ce qui indiquerait que si vous ajoutez des champs que vous obtenez seulement les champs ajoutés.
Je voudrais que tous les champs de la requête AINSI QUE ceux que j'ajouter.
Est-ce possible ou dois-je ajouter tous les champs que je utilise aussi bien?
La solution
Delphi a maintenant la possibilité de combiner les champs générés automatiquement et les champs calculés: Data.DB.TFieldOptions.AutoCreateMode une énumération de type TFieldsAutoCreationMode . De cette façon, vous pouvez ajouter vos champs calculés lors de l'exécution. François a écrit dans sa réponse comment ajouter un champ à l'exécution.
Différents modes de TFieldsAutoCreationMode:
-
acExclusive
Quand il n'y a pas de champs persistants à tous, alors les champs automatiques sont créés. Ceci est le mode par défaut.
-
acCombineComputed
Les champs automatiques sont créés lorsque l'ensemble de données n'a pas de champs persistants ou il n'y a que des champs calculés persistants. Ceci est un moyen pratique pour créer les champs persistants calculés au moment de la conception et de laisser le jeu de données créer des champs de données automatiques.
-
acCombineAlways
Les champs automatiques pour les champs de base de données seront créés quand il n'y a pas de champs persistants.
Autres conseils
Rien ne vous empêche de créer tous les champs d'abord dans votre code,
puis ajoutez vos champs calculés.
Vous pouvez utiliser un « type piraté » à utiliser les CreateFields protégées:
type
THackQuery = class(TADOQuery)
end;
[...]
MyQuery.FieldDefs.Update;
THackQuery(MyQuery).CreateFields;
ou emprunter un code de 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]);
puis créez vos champs calculés:
MyQueryMyField := TStringField.Create(MyQuery);
with MyQueryMyField do
begin
Name := 'MyQueryMyField';
FieldKind := fkCalculated;
FieldName := 'MyField';
Size := 10;
DataSet := MyQuery;
end;
Vous devez ajouter tous les champs en plus de votre champ calculé.
Une fois que vous ajoutez un champ, vous devez ajouter tous les champs que vous voulez dans l'ensemble de données.
Delphi appelle ces champs persistants par rapport à des champs dynamiques. Tous les champs sont soit persistant ou dynamique. Malheureusement, vous ne pouvez pas avoir un mélange des deux.
Une autre chose à noter, à partir de la documentation est
champs persistants listes de composants sont stockés dans votre application, et ne pas changer, même si la structure d'un base de données sous-jacente est un ensemble de données changé.
Alors, soyez prudent, si vous ajoutez ultérieurement des champs supplémentaires à une table, vous devez ajouter les nouveaux champs au composant. Même chose avec la suppression des champs.
Si vous ne voulez vraiment pas de champs persistants, il y a une autre solution. Sur une grille ou un contrôle qui doit montrer le champ calculé, vous pouvez dessiner sur mesure il. Par exemple, de nombreux contrôles de la grille ont un événement OnCustomDraw. Vous pouvez y faire votre calcul.
Si vous avez connaître vos champs pour être calculés noms lors de l'exécution, vous pouvez utiliser quelque chose comme ça.
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;