tags:

views:

284

answers:

6

I have the following scenario:

procedure SomeMethod(SomeList: array of string);

I have to call this method with some IDs from a DataSet, I know I can do it this way:

var
  MyArray: array of string;
  I: Integer;
begin
  SetLength(MyArray, MyDataSet.RecordCount);

  I := 0;
  MyDataSet.First;
  while not MyDataSet.Eof do
  begin
    MyArray[I] := MyDataSetID.Value;
    Inc(I);
    MyDataSet.Next;
  end;
  SomeMethod(MyArray);
end;

I'm lazy as hell and this is too much work for my liking... I want an easier way for doing this, any ideas?

+2  A: 

That's about the only way I can think of to do it. However, why can't you just take what you already have and turn it into a function? You'd just have to call it when you need it.

If you need to use it with different datasets, simply pass the dataset and the field to use as parameters:

procedure FieldToArray(const DS: TDataSet; const FieldName: string; 
    const Arr: TStringArray);
var
  i: Integer;
begin
  SetLength(Arr, DS.RecordCount);
  i := 0;
  DS.First;
  while not DS.Eof do
  begin
    Arr[i] := DS.FieldByName(FieldName).AsString;
    Inc(i);
    DS.Next;
  end;
end;

Somewhere visible (maybe in the interface section of the unit that declares this function), declare the TStringArray type:

type
  TStringArray = array of string;

Now in your code, anywhere you need that list of field values:

var
  MyArray: TStringArray;

FieldToArray(MyDataSet, 'ID', MyArray);
SomeProc(MyArray);

FieldToArray(AnotherDataSet, 'LastName', MyArray);
SomeOtherProc(MyArray);
MyArray := nil;
Ken White
you need an Inc(I) in your while loop...
Scott W
+1  A: 

In addition to the answer of Ken White. Repeatedly calling FieldByName can be a serious performance drain. So you can use a variable to store the field (which is updated with the dataset).

procedure FieldToArray(const DS: TDataSet; const FieldName: string; 
    const Arr: TStringArray);
var
  i:  Integer;
  field : TField;
begin
  SetLength(Arr, DS.RecordCount);
  i := 0;
  DS.First;
  field := DS.FieldByName(FieldName);
  while not DS.Eof do
  begin
    Arr[i] := field.AsString;
    DS.Next;
    Inc(i);
  end;
end;
Gamecat
I thought about that, but didn't make the change. I also thought about passing in the TField as a param instead of the FieldName.
Ken White
Eeek, you perpetuated the bug from Ken's code! You need an Inc(I) in the loop.
Scott W
Copy and paste can be a killer sometimes. <g>
Ken White
Lol, I'm having a flu and several nights with bad sleep. But you are right ;-).
Gamecat
+1  A: 

As far as I know there is no easier way built into Delphi, but there are two things which could make your life easier:

  1. Write your own DataSetToArray function. (As proposed by the other answers)
  2. If you are using Delphi 2007 or newer you can add a ToArray function to the dataset using a helper class:

    TDataSetHelper = class helper for TDataSet
      function ToArray: TStringArray;
    end;
    

That way all your datasets would have this function and you could just use:

SomeMethod(MyDataset.ToArray);
DR
Helpers also work on 2007.
Osama ALASSIRY
Of course, you are right. Fixed it.
DR
A: 

I would advise to never use TDataSet.RecordCount unless you know exactly how the data may change at any given time, and what data access you will use in the future. First getting the number of records and then loop through them has the following problems:

  • If this is not executed in one transaction it may be possible that the number of records changes while you are executing your loop. This can lead to bad crashes - the array may actually be too short.

  • If a SQL server is behind the TDataSet then you may end up fetching the result set twice, once for getting the number of records, then for fetching the data.

For that reason I don't see the benefit of using an array of strings over a TStrings object. And calling TStrings.Add() in a loop is about the shortest code you can have.

mghie
I actually thought that TStrings would be a better solution, but that's not the question the OP asked. I personally rarely use dynamic arrays.
Ken White
@Ken: True, but I still felt it worth pointing out that using TStrings would result in shorter / simpler code, which the OP was asking about.
mghie
@mghie: Good point. I should have at least mentioned it in my reply; thanks for picking up the omission.
Ken White
Because of this I mostly use TClientDatasets, as since data is cached and TDataset.RecordCount is harmless. Your worries really applies on non-cached datasets (DBX, for example) or datasets with server cursors. AFAIK, most TDataset descendants caches the data so RecordCount is stable.
Fabricio Araujo
A: 

I am also a lazy programmer. I used to be employed as a VB6 programmer and I sort of liked all their CInt, CStr, CSomethingOrOther functions and I set up some similar ones in my own common unit. One of the first ones I did was the CArray function and I made several versions of it using the OVERRIDE declaration. I have one for use with StringLists, one for delimited strings, and also one for datasets. So I might do something like

  while not DS.Eof do
  begin
    MyValue := CArray(DS)[4];
    DS.Next;
  end;

I cheated on this a bit because I used the index of the field. So my solution sort of sucks in that regard, but it's kind of nice being able to use CArray for a lot of different scenarios and it turns things like StringLists and datasets into things you can put a watch on and check for when you get to a specific value.

A: 

Does it absolutely have to be "an array of strings"? Just about anything you can do with a dynamic array, you can use a List for and do it more easily. Here's how I'd do it

procedure SomeMethod(SomeList: TStringList);

...

var
  MyList: TStringList;
begin
  MyList := TStringList.Create;
  MyDataSet.First;
  while not MyDataSet.Eof do
  begin
    MyList.add(MyDataSetID.Value);
    MyDataSet.Next;
  end;
  SomeMethod(MyList);
end;

Make sure you free your TStringList when you're done with it, of course.

Mason Wheeler