views:

438

answers:

4

In my app I have different forms that use the same datasource (so the queries are the same too), defined in a common datamodule. Question is, is there a way to know how many times did I open a specific query? By being able to do this, I could avoid close that query without closing it "every where else".

Edit: It's important to mention that I'm using Delphi3 and it is not a single query but several.

A: 

You could have a generic TDataSet on the shared datamodule and set it on the OnDataChange, using the DataSet property of the Field parameter

dstDataSet := Field.DataSet;

This way, when you want to close the dataset, close the dataset on the datamodule, which is a pointer to the correct DataSet on some form you don't even have to know

Pascal
Again, this implies that a lot of modifications should be done, but I appreciate the insight anyway! Thanks Pascal!
Eliseo Ocampos
+3  A: 

You can get creative using a addref/release like approach. Just create a few functions and an integer variable in your shared datamodule to do the magic, and be sure to call them..partial code follows:

TDMShared = class(tDataModule)
  private
    fQueryCount : integer; // set to 0 in constructor
  public
    function GetQuery : tDataset;
    procedure CloseQuery; 
  end;

function TDMShared.GetQuery : tDataset;
begin
  inc(fQueryCount);
  if fQueryCount = 1 then
    SharedDatsetQry.open;
  Result := shareddatasetqry; // your shared dataset here
end;

procedure TDMShared.CloseQuery;
begin
  dec(fQueryCount);
  if fQueryCount <= 0 then
    shareddatasetqry.close; // close only when no refs left.
end;

EDIT: To do this with multiple queries, you need a container to hold the query references, and a way to manipulate them. a tList works well for this. You will need to make appropriate changes for your TDataset descendant, as well as create a FreeAndNil function if you are using an older version of Delphi. The concept I used for this was to maintain a list of all queries you request and manipulate them by the handle which is in effect the index of the query in the list. The method FreeUnusedQueries is there to free any objects which no longer have a reference...this can also be done as part of the close query method, but I separated it to handle the cases where a specific query would need to be reopened by another module.

  Procedure TDMShared.DataModuleCreate(Sender:tObject);
  begin
    dsList := tList.create;
  end;

  Function TDMShared.CreateQuery(aSql:String):integer;
  var
    ds : tAdoDataset;
  begin
    // create your dataset here, for this example using TADODataset
    ds := tAdoDataset.create(nil); // self managed
    ds.connection := database;
    ds.commandtext := aSql;
    ds.tag := 0;
    Result := dsList.add(ds);
  end;

  function TDMShared.GetQuery( handle : integer ) : tDataset;
  begin
    result := nil;
    if handle > dsList.count-1 then exit; 
    if dsList.Items[ handle ] = nil then exit; // handle already closed
    result := tAdoDataset( dsList.items[ handle ]);
    Inc(Result.tag);
    if Result.Tag = 1 then
      Result.Open;    
  end;  

  procedure TDMShared.CloseQuery( handle : integer );
  var
    ds : tAdoDataset;
  begin
    if handle > dsLIst.count-1 then exit;
    ds := tAdoDataset( dsList.items[ handle ]);
    dec(ds.Tag);
    if ds.Tag <= 0 then
      ds.close;
  end;

  procedure TDMShared.FreeUnusedQueries;
  var
    ds : tAdoDataset;
    ix : integer;
  begin
    for ix := 0 to dsList.Count - 1 do
      begin
        ds := tAdoDataset(dsLIst.Items[ ix ]);
        if ds.tag <= 0 then
          FreeAndNil(dsList.Items[ix]);
      end;
  end;

procedure TDMShared.DataModuleDestroy(Sender: TObject);
var
  ix : integer;
begin
  for ix := 0 to dsList.count-1 do
    begin
      if dsLIst.Items[ix] <> nil then
        FreeAndNil(dsLIst.Items[ix]);      
    end;
  dsList.free;  
end;
skamradt
Thanks skamradt, the problem with this approach is that I have to modified a lot more of code, because it is not a single query but several.
Eliseo Ocampos
I see your point, pretty clever, but by applying this, it will implies reestructuration of several units what I'm unable to do rigth now :/ I'm looking for something like a quick-hack to apply directly *before* I close a shared query. But thanks anyway!
Eliseo Ocampos
+4  A: 

The idea is to use the DataLinks property of the TDataSource.
But, as it is protected, you have to gain access to it. One common trick is to create a fake descendant just for the purpose of casting:

type
  TDataSourceHack = class(TDataSource);

Then you use it like:

  IsUsed := TDataSourceHack(DataSource1).DataLinks.Count > 0;
François
Thanks a lot François, this is what I was looking for, unfortunatelly I didn't mention that I'm working with Delphi3 :( my mistake..You can't use this method in Delphi3 because the field FDataLinks is private and have no property related to it. In the other hand, in Delphi6 you have the published property DataLinks which you can use with no problem.
Eliseo Ocampos
For Delphi 3 you may want to try this "dirtier" hack from HALLVARD VASSBOTN...: http://hallvards.blogspot.com/2004/06/hack-5-access-to-private-fields.html
François
Aaaggghh.. no way to apply this solution neither (or maybe I did it wrong), I got access violations exceptions for every where.
Eliseo Ocampos
A: 

Ok, a completely different solution...one that should work for Delphi 3.

Create a new "Descendant Object" from your existing dataset into a new unit, and add some behavior in the new object. Unfortunately I do not have Delphi 3 available for testing, but it should work if you can find the proper access points. For example:

TMySharedDataset = class(tOriginalDataset)
private
  fOpenCount : integer;
protected
  procedure Internal_Open; override;
  procedure Internal_Close; override;
end;

TMySharedDataset.Internal_Open;
begin
  inherited Internal_Open;
  inc(fOpenCount);
end;

TMySharedDataset.Internal_Close;
begin
  dec(fOpenCount);
  if fOpenCount <= 0 then
    Inherited Internal_Close;
end;

Then just include the unit in your data module, and change the reference to your shared dataset (you will also have to register this one and add it to the palette if your using components). Once this is done, you won't have to make changes to the other units as the dataset is still a descendant of your original one. What makes this all work is the creation of YOUR overridden object.

skamradt