2

So, lately we (me and my coworkers) have been chatting about migrating to FireDac, we are currently using IBO and DBX, but mostly IBO. And then we decided to take everything from IBO to FireDac, but entering in every form, changing every IBOQuery, adding all fields, settings all the display format, etc, etc, etc, would take too much time, so we decided to make a component do it, seemed like an easy task, but I just started and I'm already stuck in something that seems simple, but that I never came across before. First let's look at the component code:

    unit UMyComponent;

    interface

    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IB_Components, IB_Access,
      IBODataset, Vcl.StdCtrls, Vcl.Buttons, Vcl.Grids, Vcl.DBGrids, Data.DB,
      uADStanIntf, uADStanOption, uADStanParam, uADStanError,
      uADDatSManager, uADPhysIntf, uADDAptIntf, uADStanAsync, uADDAptManager,
      uADCompDataSet, uADCompClient;

    type
      TMyComponent = class(TComponent)
      private
        FADConnection: TADConnection;
        FConverter: String;

        procedure Iniciar;

        procedure SetADConnection(const Value: TADConnection);
        procedure SetConverter(const Value: String);
      published
        property Converter: String read FConverter write SetConverter;
        property ADConnection: TADConnection read FADConnection write SetADConnection;
      end;

    procedure Register;

    implementation

    procedure Register;
    begin
      RegisterComponents('MyComponents', [TMyComponent]);
    end;

    { TMyComponent }

    procedure TMyComponent.Iniciar;
    var
      Form: TForm;
      IBOQuery: TIBOQuery;
      i: Integer;

      procedure _ConverterIBOQuery;
      var
        ADQuery: TADQuery;
        qName: String;
      begin
        qName := IBOQuery.Name;

        if qName.Contains('OLD_') then
          Exit;

        IBOQuery.Name := 'OLD_'+ qName;

        if (FindComponent(qName) = nil) then
        begin
          ADQuery            := TADQuery.Create(Form);
          ADQuery.Name       := qName;
          ADQuery.Connection := FADConnection;
          ADQuery.SQL        := IBOQuery.SQL;

          {
           I need to add the fields here, but I need them having a reference,
           like the ones you Right Click > Fields Editor > Add All Fields (CTRL + F)
           because in the final form of this component, it won't rename the old query
           with an 'OLD_' prefix, it will destroy it, and the fields will be gone too,
           so I need to add them (having the reference) in order to not rewrite any of my code
          }
        end;
      end;
    begin
      if Owner is TForm then
        Form := TForm(Owner);

      if Assigned(Form) then
      begin
        for i := 0 to (Form.ComponentCount -1) do
          {
           I know it will stop in the first query it come across,
           but I'm trying to full convert only one to know if it's actually possible
          }
          if (Form.Components[i] is TIBOQuery) then
          begin
            IBOQuery := TIBOQuery(Form.Components[i]);
            Break;
          end;

        if Assigned(IBOQuery) then
          _ConverterIBOQuery;
      end;
    end;

    procedure TMyComponent.SetConverter(const Value: String);
    begin
      FConverter := UpperCase(Value[1]);

      if (FConverter = 'S') then
        Iniciar;

      FConverter := '';
    end;

    procedure TMyComponent.SetADConnection(const Value: TADConnection);
    begin
      FADConnection := Value;
    end;

    end.

I already tried some of methods found on the internet, such as:

  • Creating a variable of TField
  • Using FieldDefs/FieldDefList, updating them and creating the fields
  • "Hacking" the ADQuery with a "fake" class in order to use the CreateFields procedure

And none of them did what I was expecting, so I'm questioning

Can I create the field references via code? And, if it's possible, how?

And with references I mean, for example, you have IBOQuery1, and the SQL is

SELECT NAME
FROM COUNTRY

After that, you go to the Fields Editor > Add All Fields (CTRL + F), and then you have the reference IBOQuery1NAME, which is a TStringField and you can just call IBOQuery1NAME.AsString instead of IBOQuery1.FieldByName('NAME').AsString

TL;DR

Trying to create a component that migrate a IBOQuery to ADQuery, but I can't create the references

  • "component that migrate a IBOQuery to ADQuery" Well, FireDAC has its TFDBatchMove component to assist with that sort of thing. Have you looked at it? – MartynA Nov 10 '15 at 19:21
  • Seems like it's only available for XE7 or above, and, unfortunately, I'm using XE3, but after a brief read, it isn't exactly what I want. I will continue looking into it, maybe it was my reading that was on fault, but seems like not what I expected. – Geovane Silveira Nov 10 '15 at 19:31
  • Well, you could copy the table data in code. How to do it best though depends on how many data rows are involved. A few thousand is quite a different problem than several million. How many do you have? Anyway, if you are using Ado with a Sql Server back-end, I would suggest you consider using Delphi's standard Ado components instead of FireDAC. FireDAC's quest to be all things to all men and the levels of abstraction it can involve sometimes gets in the way if you don't need some of its more arcane features. – MartynA Nov 10 '15 at 19:40
  • In average we have about 100 rows per query and an average of 3 queries per form, and we have already considered copying in that way, but if we could add them like it was meant to be added (not copying the code), then it would be better – Geovane Silveira Nov 10 '15 at 19:47

1 Answers1

2

After many attempts and research, I found an old question with a problem similar to mine, and happily there was a answer with exactly what I wanted

How to add a field programatically to a TAdoTable in Delphi

The answer was provided by the user: Мסž

procedure AddAllFields(DataSet: TDataset);
var
   FieldsList: TStringList;
   FieldName: WideString;
   Field: TField;
   WasActive: boolean;
   FieldDef: TFieldDef;
   i: Integer;
begin
   WasActive := DataSet.Active;
   if WasActive then
      DataSet.Active := False;
   try
      FieldsList := TStringList.Create;
      try
         DataSet.FieldDefs.Update;

         // make a list of all the field names that aren't already on the DataSet
         for i := 0 to DataSet.FieldDefList.Count - 1 do
            with DataSet.FieldDefList[i] do
               if (FieldClass <> nil) and not(faHiddenCol in Attributes) then
               begin
                  FieldName := DataSet.FieldDefList.Strings[i];
                  Field := DataSet.FindField(FieldName);
                  if (Field = nil) or (Field.Owner <> DataSet.Owner) then
                     FieldsList.Add(FieldName);
               end;

         // add those fields to the dataset
         for i := 0 to FieldsList.Count - 1 do
         begin
            FieldDef := DataSet.FieldDefList.FieldByName(FieldName);
            Field := FieldDef.CreateField(DataSet.Owner, nil, FieldName, False);
            try
               Field.name := FieldName + IntToStr(random(MaxInt)); // make the name unique
            except
               Field.Free;
               raise ;
            end;
         end;
      finally
         FieldsList.Free;
      end;
   finally
      if WasActive then
         DataSet.Active := true;
   end;
end;
Community
  • 1
  • 1
  • 1
    My final version has some modifications, because it didn't work 100% as I wanted, but it did what I needed first, so I'm grateful I found it – Geovane Silveira Nov 20 '15 at 12:58