Search code examples
databasedelphidatasettclientdataset

How to change Clientdataset field datatype at runtime


For Delphi ClientDataSets where fields have been define at design time, is there a way at runtime to change a specific field's datatype ( change the cds.Fields[n].DataType) ?

I have a legacy Delphi 7 program with both SQLDataSet and ClientDataSet fields set at design time (in order to override various properties).

These are connected to a 3rd-party Sybase SQL Anywhere 11 database.

Recently the vendor changed all 'Description' fields from VarChar(128) to long varchar, but only for certain of his customers. So, my code has to support both types of fields when I query on these 'Description' fields.

I was hoping to set conditional compilation on the class field types (then add the fields before opening the SQL/CLient Dataset), but the compiler ignores {$IFDEF } conditionals in the component definition section of the class (which, the more I think about it, makes good sense)!

There are dozens of modules, with hundreds of fields affected, so any insight is appreciated.


Solution

  • I have also faced this problem before, not with CDS, but with TADODataSet using persistent fields at design time. I think that the code below will help you get the idea of how to fix/patch your CDS datasets.

    The idea is to query the relevant table schema; get the actual fileds data type; and "change" the persistent field type by un-attaching it from the DataSet and adding a new matching persistent filed instead:

    // TData class    
    procedure TData.DataModuleCreate(Sender: TObject);
    var
      I: Integer;
    begin
      for I := 0 to ComponentCount - 1 do
        if (Components[I] is TCustomADODataSet) then
          DataSetPrepareMemoFields(TDataSet(Components[I]));
    end;
    
    procedure TData.DataSetPrepareMemoFields(DataSet: TDataSet);
    var
      Fld: TField;
      I: Integer;
      FldName, CompName: string;
      AOwner: TComponent;
    begin
      // Here you need to query the actual table schema from the database 
      // e.g. ADOConnection.GetFieldNames and act accordingly
    
      // check which DataSet you need to change
      // if (DataSet = dsOrders) or ... then... 
    
      if DataSet.FieldList.Count > 0 then
        for I := DataSet.FieldList.Count - 1 downto 0 do 
        begin
          if DataSet.FieldList.Fields[I].ClassNameIs('TMemoField') and (DataSet.FieldList.Fields[I].FieldKind = fkData) then 
          begin
            // save TMemoField properties
            AOwner := DataSet.FieldList[I].Owner;
            CompName := DataSet.FieldList[I].Name;
            FldName := DataSet.FieldList.Fields[I].FieldName;
            // dispose of TMemoField
            DataSet.FieldList[I].DataSet := nil; // Un-Attach it from the DataSet
            // create TWideADOMemoField instead
            Fld := TWideADOMemoField.Create(AOwner); // Create new persistent Filed instead 
            Fld.Name := CompName + '_W';
            Fld.FieldName := FldName;
            Fld.DataSet := DataSet;
          end;
        end;
    end;
    

    That said, After I have fixed that issue, I have never ever used persistent fields again. All my fields are generated in run time. including calculated/lookup/internal fields.