0

I have a MasterQry and a SlaveQry on the form . The MasterQry is something like this :

select * from Header where Active = 1 .

On it's AfterScroll event I have following :

select * from Slave where HeadID=:Header.ID

Now if I do :

if MasterQry.Active then MasterQry.Close;
MasterQry.Open;

this works flawlessly if I have more then one record , but it does not work if I have only one .

Even if I do MasterQry.First; nothing happens.

If I try MasterQry.AfterScroll(MasterQry) I get an access violation .

I was refactoring my code and was trying to make it more compact , because I did a lot of Open Close Locate ID ( needed to refresh data to get actual status, whether it is locked etc. ) and I did this :

function RefreshQuery(AQuery : TADOQuery; ID : integer) : boolean ; overload;
var AfterOpen,AfterScroll : TDataSetNotifyEvent;
begin
  result:=false;

  AfterOpen := AQuery.AfterOpen;
  AfterScroll := AQuery.AfterScroll;

  AQuery.AfterOpen:=nil;
  AQuery.AfterScroll:=nil;

  if AQuery.Active then AQuery.Close;
  AQuery.Open;

  if not AQuery.Locate('id', ID, []) then
    result:=false
  else
    result:=true;

  AQuery.AfterOpen:=AfterOpen;
  AQuery.AfterScroll:=AfterScroll;
  if Assigned(AQuery.AfterScroll) then
    AQuery.AfterScroll(AQuery);
end;

Please NOTE this code is not universal but suits my needs perfectly . What I noticed is that the AfterScroll event here is being triggered even if I have only One Record in the MasterQry , or even if I have non at all . I was really happy , I tested it multiple times and it delivered correct results.

I checked the TADOQuery.First and TADOQuery.Locate procedures and both had DoAfterScroll but it was not triggered with One Record or with No Record . ( the SlaveQry was left open in a undesired state )

I've googled a lot for this but was unable to find the reason .

My Question is : why does this work ? why does AfterScroll fires with One or No Record .

Thank you.

UPDATE

I can only reproduce this with Microsoft SQL . So in order to test this you need . Two Tables .

In MasterTable add two records ( ID , Text, Active )

1 First 1

2 Second 1

in the SlaveTable add two or more records ( ID,HeadID,Text )

1,1,First-1

2,1,First-2

3,2,Second-1

4,2,Second-2

Now drop on the Form one ADOConnection two ADOQueries .

in the MainQuery you have following text

Select * from MasterTable where Active=1

in the SlaveQuery you have following text

select * from SlaveTable where HeadID=:HeadID

on the MainQuery.BeforeOpen you have this :

MainQuery.AfterScroll:=nil;

on the MainQuery.AfterScroll you have this :

if SlaveQuery.Active then SlaveQuery.Close;
SlaveQuery.Parameters.ParamByName('HeadID').Value:=MainQueryID.Value;
SlaveQuery.Open;

on the MainQuery.AfterOpen you have this :

MainQuery.AfterScroll:=MainQueryAfterScroll;

Add a Button to this form :

Button1Click Event Contains following :

if MasterQuery.Active then MasterQuery.Close;
MasterQuery.Open;

So if you now attach a Grid to both Queries , you can see it is following perfectly .

Without closing the Program , go into the SQL Server Manager and run the following update statement :

update MasterTable set Active=0

Press the Button1 on the Form again :

The MasterQuery is Emtpy , the SlaveQuery was left in the Last Open State .

In order to fix this you need to alter the Button1Click as follows :

var AfterOpen,AfterScroll : TDataSetNotifyEvent;
begin
  AfterOpen := AQuery.AfterOpen;
  AfterScroll := AQuery.AfterScroll;

  AQuery.AfterOpen:=nil;
  AQuery.AfterScroll:=nil;

  if AQuery.Active then AQuery.Close;
  AQuery.Open;

  AQuery.AfterOpen:=AfterOpen;
  AQuery.AfterScroll:=AfterScroll;
  if Assigned(AQuery.AfterScroll) then
    AQuery.AfterScroll(AQuery);
end;

And now it works . I don't know why because MasterQuery.First should trigger DoAfterScroll but nothing Happens . It seems like Setting AfterScroll to nil and then back again somehow triggers AfterScroll even when it has 1 Record or is empty .

user1937012
  • 1,031
  • 11
  • 20
  • Your q seems self-contradictory: you say "this works flawlessly if I have more then one record , but it does not work if I have only one" but then "why does AfterScroll fires with One or No Record". Anyway, `AfterScroll` will occur even if there is only one record in the dataset (or none). – MartynA Feb 14 '19 at 07:40
  • AfterScroll does not work with One or No Record . And the only reason this works is that it seems by setting AfterScroll to nil , and then Back to AQuery.AfterScroll:=AfterScroll somehow triggers it . Simply doing a if MasterQry.Active then MasterQry.Close; MasterQry.Open; does not trigger AfterScroll; With One or No Record . I tested it multiple times . My Question is why? ( I don't understand ) – user1937012 Feb 14 '19 at 07:55
  • No Record happens naturally only if you alread opened the MasterQry and someone alters the Data and is no longer visible . If you Re-Open the MasterQry the data is no longer there . The AfterSCroll is not triggered. ANd the SlaveQry stays Open with last known info . – user1937012 Feb 14 '19 at 07:57
  • Sorry, but If you have no idea then you should not answer me . Thank you – user1937012 Feb 14 '19 at 10:41
  • See my new answer. I stick by what I said about your code having become confused, as has your q. I've added a simple illustration of how to refresh the data in case another user has made changes to the table since it was opened. After doing the refresh, the Master grid correctly shows changes made by another user via Sql Server Management Studio. – MartynA Feb 14 '19 at 12:23

1 Answers1

4

As I said in a comment, most of the code in your RefreshQuery shouldn't be necessary, as linking Master->Detail datasets should "just work". In fact, your RefreshQueryshouldn't be necessary at all.

I created a minimal project based on your master and slave tables just by dropping components from the pallete, wiring them up and adding ONLY the code in Form1.FormCreate below. The contents of the Slave grid correctly track the Master grid, including the case where there are no matching Slave records, i.e. the Slave grid displays empty. Notice there are no data events whatever needed, i.e no AfterScroll and no Locate, etc, calls.

  type
    TForm1 = class(TForm)
      dsMaster: TDataSource;
      DBGrid1: TDBGrid;
      DBNavigator1: TDBNavigator;
      DBGrid2: TDBGrid;
      DataSource2: TDataSource;
      DBNavigator2: TDBNavigator;
      ADOConnection1: TADOConnection;
      qMaster: TADOQuery;
      qSlave: TADOQuery;
      qSlaveID: TIntegerField;
      qSlaveHeaderID: TIntegerField;
      qSlaveAText: TWideStringField;
      procedure FormCreate(Sender: TObject);
    public
    end;

  [...]

  procedure TForm1.FormCreate(Sender: TObject);
  begin
    qMaster.SQL.Text := 'select * from mastertable';

    qSlave.DataSource := dsMaster;
    qSlave.SQL.Text := 'select * from slavetable where headerid = :id';
    //  NOTE: because the DataSource property of qSlave is set to dsMaster,
    //  the ` = :id` tells the Ado run-time code to get the value of the
    //  ID field in the qMaster table.

    qMaster.Open;
    qSlave.Open;
  end;

If you want to refresh the Master or Slave table in case another user has changed records in it, you can do that like this:

procedure TForm1.Button1Click(Sender: TObject);
begin
  qMaster.Refresh;
end;

but be aware that the table needs to be correctly set up on the Sql Server. As long as the ID field is set up as a primary key, and/or there is a unique index set up on it on the server, the call to Refresh should work fine, but if not you will get an error with a message to the effect of "Insufficient key information for updating or refreshing." You could of course do the refresh on a timer (but don't call it too frequently, i.e. more than once every few seconds).

MartynA
  • 30,454
  • 4
  • 32
  • 73
  • are you sure that is all the code ? did you do something visually besides setting the connection string for both queries ? – user1937012 Feb 14 '19 at 12:30
  • Yes. sorry, I set the connection string. But that was the **only** thing I set in the IDE apart from wiring the components together. The **entire** code of the project is shown below and it wotks 100% correctly. Try it, it will only take you 10 minutes to set it up from scratch. – MartynA Feb 14 '19 at 12:32
  • it does work , a worthy alternative it is . I would still like to know why my example works if I set AfterScroll to nil and then back again . I do some other calculations there as well... on AfterScroll.. which you cannot do here... Though yours is slower on large tables with Locate . But non the less it works. – user1937012 Feb 14 '19 at 13:11
  • Well, I not not have your entire code, so am not well-placed to speculate, I'm afraid. Your best bet would be to simplify your code as far as possible and then trace it line-by-line in the debugger. If you can boil it down to a small example, post it as a new q and I'll take a look. – MartynA Feb 14 '19 at 13:18