Debugging helper for TDataSet

I like programming in Delphi, but I don’t particularly like writing applications that work with database backends, but sometimes I just can’t avoid it (have been working on one for several weeks now, and it wasn’t too bad really). But one thing that can drive me nuts is when you have got an error, want to investigate the cause and it’s a bloody nuisance to try and get the value of the current record in a dataset. So, finally I wrote this little helper unit which I want to share with you.

All it does is export one public function TDataset_Dump which is meant to be called from the debugger’s “Evaluate and Modify” window:

TDataset_Dump(SomeDataset)

If called like this, it will magically open a text file in the associated application and display the content of the current record of the dataset you passed to it.

Alternatively you can let it dump the whole dataset like this:

TDataset_Dump(SomeDataset, -1)

Or all remaining records in the dataset (until EOF) like this:

TDataset_Dump(SomeDataset, 0)

Or you can pass it a number of records it should dump:

TDataset_Dump(SomeDataset, 5)

For this to work, you need to add the unit u_dzDatasetDump to the unit with the dataset you want to debug, otherwise the debugger won’t know about the function.

The unit is part of my dzlib library. It needs a few units from it (I’m a lazy bastard ™, so since I use the library in most of my programs I didn’t see the point of trying to avoid dependencies, but it should’t be too difficult to remove them.).

OK, so here goes the unit:

///<summary>
/// Add this unit only for debug purposes.
/// It exports TDataset_Dump for dumping a dataset from the evaluate and modify dialog
/// of the Delphi debugger. </summary>
unit u_dzDatasetDump;

interface

uses
  SysUtils,
  Classes,
  DB;

///<summary>
/// @param Count:  1 -> Dump the current record only (default)
///               -1 -> Dump the whole dataset
///                0 -> Dump from the current record until EOF
///               >1 -> Dump a maximum of n records starting from the current
/// @returns the file name the data was written to </summary>
function TDataset_Dump(_ds: TDataset; _Count: Integer = 1): string;

implementation

uses
  u_dzLineBuilder,
  u_dzVariantUtils,
  u_dzFileUtils,
  u_dzExecutor,
  u_dzOsUtils;

const
  DUMP_ALL = -1;

procedure TDataset_DumpHeaders(_ds: TDataset; _sl: TStrings);
var
  lb: TLineBuilder;
  i: Integer;
  fld: TField;
begin
  lb := TLineBuilder.Create;
  try
    for i := 0 to _ds.FieldCount - 1 do begin
      fld := _ds.Fields[i];
      lb.Add(fld.FieldName);
    end;
    _sl.Add(lb.Content)
  finally
    FreeAndNil(lb);
  end;
end;

procedure TDataset_DumpCurrent(_ds: TDataset; _sl: TStrings);
var
  lb: TLineBuilder;
  i: Integer;
  fld: TField;
begin
  lb := TLineBuilder.Create;
  try
    for i := 0 to _ds.FieldCount - 1 do begin
      fld := _ds.Fields[i];
      lb.Add(Var2Str(fld.Value));
    end;
    _sl.Add(lb.Content)
  finally
    FreeAndNil(lb);
  end;
end;

procedure TDataset_DumpToEof(_ds: TDataset; _sl: TStrings);
begin
  while not _ds.Eof do begin
    TDataset_DumpCurrent(_ds, _sl);
    _ds.Next;
  end;
end;

procedure TDataset_DumpAll(_ds: TDataset; _sl: TStrings);
begin
  _ds.First;
  TDataset_DumpToEof(_ds, _sl);
end;

function TDataset_Dump(_ds: TDataset; _Count: Integer = 1): string;
var
  sl: TStringList;
  bm: Pointer;
begin
  Result := '';
  sl := TStringList.Create;
  try
    if not Assigned(_ds) then begin
      sl.Add('<dataset not assigned>');
      if _Count = -1 then begin
        // do nothing this is a dummy call to fool the linker
        Exit; //==>
      end;
    end else if not _ds.Active then begin
      sl.Add('<dataset not active>');
    end else if _ds.IsEmpty then begin
      sl.Add('<dataset is empty>');
    end else begin
      bm := nil;
      _ds.DisableControls;
      try
        bm := _ds.GetBookmark;
        TDataset_DumpHeaders(_ds, sl);
        if _Count = DUMP_ALL then begin
          TDataset_DumpAll(_ds, sl);
        end else begin
          if _ds.Eof then begin
            sl.Add('<dataset is at eof>');
          end else if _Count = 0 then begin
            TDataset_DumpToEof(_ds, sl);
          end else begin
            while (not _ds.Eof) and (_Count > 0) do begin
              TDataset_DumpCurrent(_ds, sl);
              _ds.Next;
              Dec(_Count);
            end;
          end;
        end;
      finally
        _ds.GotoBookmark(bm);
        _ds.EnableControls;
      end;
    end;
    if Assigned(_ds) then
      Result := _ds.Name;
    if Result = '' then
      Result := 'NONAME';
    Result := itpd(TFileSystem.GetTempPath) + 'dump_of_' + Result + '.txt';
    sl.SaveToFile(Result);
    OpenFileWithAssociatedApp(Result, True);
  finally
    FreeAndNil(sl);
  end;
end;

initialization
  // Make sure the linker doesn't eliminate the function
  TDataset_Dump(nil, -1);
end.