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 .

4

1 回答 1

4

正如我在评论中所说,您中的大多数代码RefreshQuery都不是必需的,因为链接 Master->Detail 数据集应该“正常工作”。事实上,你RefreshQuery根本不需要。

我创建了一个基于您的主表和从表的最小项目,只需从托盘中删除组件,将它们连接起来并仅在下面的 Form1.FormCreate 中添加代码。从网格的内容正确地跟踪主网格,包括没有匹配的从记录的情况,即从网格显示为空。请注意,没有任何需要的数据事件,即 noAfterScroll和 noLocate等调用。

  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;

如果您想刷新 Master 或 Slave 表以防其他用户更改了其中的记录,您可以这样做:

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

但请注意,表需要在 Sql Server 上正确设置。只要 ID 字段设置为主键,和/或在服务器上设置了唯一索引,调用Refresh应该可以正常工作,但如果不是,您将收到一条错误消息“更新或刷新的关键信息不足”的效果。您当然可以在计时器上进行刷新(但不要太频繁地调用它,即每隔几秒不止一次)。

于 2019-02-14T11:02:21.473 回答