3

I have as simple query, which returns following rows:

Name  Value
Peter   1
Peter   2
Peter   3
John    1
John    2

Applying filter:

 ADO.Filter := 'Name="John"';
 ADO.Filtered := True; // Now, its only 2 rows in dataset
 ADO.Locate('Value', 2); 

Cursor should point to "John 2", but it points to "Peter 2" (which being filtered out by filter). And locate returns True.

Tried on Delphi 7, Rad studio XE 6. It seems that this error is living there for the last 15 years Any solution ?

kobik
  • 21,001
  • 4
  • 61
  • 121
Valeriy
  • 131
  • 2
  • 5
  • 1
    Please post your actual code in the future. The above does not compile due to multiple errors. – Dale M Jan 30 '15 at 09:27

2 Answers2

9

The problem with TCustomADODataSet.Locate is that it's internally using Recordset.Clone and trying to locate a record in the cloned recordset, without setting the filter to the cloned recordset (see ADODB TCustomADODataSet.LocateRecord).

From the Remarks in the docs:

The Filter property of the original Recordset, if any, will not be applied to the clone. Set the Filter property of the new Recordset to filter the results. The simplest way to copy any existing Filter value is to assign it directly, as follows. rsNew.Filter = rsOriginal.Filter The current record of a newly created clone is set to the first record.

I have been using my own simple Locate function for filtered ADO DataSets: Basically, storing the current bookmark, moving to the first record and iterating the DataSet until it found a match. If no match found restore the previous bookmark.
Bellow is a really limited implementation that worked for me (a lot of todo tho for a perfect solution):

class function TData.Locate(DataSet: TDataSet; const KeyFields: string;
  const KeyValues: Variant; Options: TLocateOptions): Boolean;
{ a very simple Locate function - todo: TLocateOptions & multiple KeyFields/KeyValues }
var
  BM: TBookmarkStr;
begin
  Result := False;
  if DataSet.IsEmpty then Exit;
  BM := DataSet.Bookmark;
  DataSet.DisableControls;
  try
    DataSet.First;
    while not DataSet.Eof do
    begin
      if DataSet.FieldByName(KeyFields).Value = KeyValues then
      begin
        Result := True;
        Break;
      end;
      DataSet.Next;
    end;
    if not Result then DataSet.Bookmark := BM;
  finally
    DataSet.EnableControls;
  end;
end;

Another option is to patch ADODB.pas TCustomADODataSet.LocateRecord and set the FLookupCursor.Filter to match the current dataset filter. This option is acceptable as long as you patch ADODB.pas as a new copy placed in your project folder.

Yet another option is to use TCustomADODataSet.Recordset.Find method (See also: How to use a RecordSet.Find with TADOQuery?).

Community
  • 1
  • 1
kobik
  • 21,001
  • 4
  • 61
  • 121
  • Thanks, its looks its true. – Valeriy Jan 30 '15 at 11:16
  • Can you post code of your locate function ? FLookupCursor.Filter is private symbol. I cannot access it from any class derived from TCustomADODataSet Patching delphi source is bad ida. I wonder why it not being fixed yet by Embercadero or Borland. This bug being there since ADO was introduced. – Valeriy Jan 30 '15 at 11:22
0

Try this:

ADO.Filter := 'Name=' + QuotedStr('John');
ADO.Filtered := True; // Now, its only 2 rows in dataset
ADO.Locate('Value',2,[]);

The third parameter of the Locate function is for locate options, which I've left empty in the example above. You can read up on Locate here http://docwiki.embarcadero.com/RADStudio/XE7/en/Using_Locate and here http://docwiki.embarcadero.com/Libraries/XE7/en/Data.DB.TDataSet.Locate

Dale M
  • 833
  • 16
  • 21