-3

I am in the process of creating a Datasnap Server and Client set, with which to eventually connect (server) to several different Databases. For the server I created a test class that I call TMember. The server and client uses this Unit (uMember).

On the server I placed a function that creates and returns a TMember to the requesting Client machine. Some of the data within a TMember object had been collected from a SQL Server DB.

On the Client side I created a Global variable (on my Client main form) of type TObjectList. Every time I fire the Create New Member function, the object is created on the server and successfully sent back down to the client. I can access the object methods and properties.

My problem, however, is iterating over this TObjectList collection. No matter which way I do it, I only get AVs. Here are my code attempts. Can anyone perhaps indicate where I am messing up?

Thank you very much in advance!

Here is the TMember interface:

    type
  TMember = class
    FFirstName : String;
    FLastName : String;
    FFullname : String;
    FAddress : String;
  private
    {private declarations}
  public
    {public declarations and properties}
    constructor Create(fname, lname : String); overload;
    constructor Create(otherMember : TMember); overload;
    destructor Destroy;
    procedure SetFName(const Value: String);
    procedure SetFullname(const Value: String);
    procedure SetLName(const Value: String);
    function GetFullname : String;
    function ToString : String;
    procedure ChangeAddr(Addr : String);

    property Name : String read FFirstName write SetFName;
    property Surname : String read FLastName write SetLName;
    property Fullname : String read GetFullname write SetFullname;
  end;

This code is used to create a new TMember object from the Server:

    procedure TfrmMainClient.btnCreateMemClick(Sender: TObject);
var
  Server : TServerMethodsClient;
  newMem : TMember;
  i : Integer;
begin
  try
    Server := TServerMethodsClient.Create(DM1.ServerConn.DBXConnection);
    newMem := Server.GetMember(edtFName.Text, '');
    if Assigned(newMem) then begin
      AllMembers.Add(newMem);
      Listbox1.Items.Add(newMem.GetFullname);
    end;

  finally
    Server.Free;
  end;
end;

So, after a while there are several objects floating around in the Client's memory. Now I would like to access them from the Listbox (as if it is a "menu" to select a TMember from). The idea is/was to record the Listbox's ItemIndex and send that to the AllMember TObjectList object collection to access the TMember sitting at that Index. This creates a lovely little AV. So, then I tried a simple iteration over the ObjectList using a basic for..do loop filling out a Listbox. Same result. I also tried a for..in loop, but to no avail.

I am pretty sure it is something silly that I am simply missing...

Here is attempt # 1:

    procedure TfrmMainClient.btnRefreshClick(Sender: TObject);
var
  nextMember : TMember;
begin
  for nextMember in AllMembers do
    begin
      Listbox1.Items.Add(nextMember.GetFullname);
    end;
end;

Here is attempt # 2:

    procedure TfrmMainClient.btnRefreshClick(Sender: TObject);
var
  nextMember : TMember;
  i : Integer;
begin
  for i := 0 to AllMembers.Count - 1 do
    begin
      nextMEmber := TMember.Create(AllMembers[i]);
      Listbox1.Items.Add(nextMember.GetFullname);
    end;
end;

Here is attempt # 3:

    procedure TfrmMainClient.btnRefreshClick(Sender: TObject);
var
  nextMember : TMember;
  i : Integer;
begin
  for i := 0 to AllMembers.Count - 1 do
    begin
      nextMember := AllMembers.Items[i];
      Listbox1.Items.Add(nextMember.GetFullname);
    end;
end;

I trust someone could assist...

Edit 1:

Change this

 procedure TfrmMainClient.btnRefreshClick(Sender: TObject);
var
  i: Integer;
  Item : TPair<Integer, TMember>;
  ActiveMember : TMember;
  Key : Integer;
begin
  Listbox1.Clear;
  for i := 0 to AllMembersKeys.Count - 1 do
    begin
      try
        ActiveMember := TMember.Create('','');

        //Get the MemberID as a Key stored in AllMembersKeys
        Key := AllMembersKeys[i];

        //Now try get this TMember record from the AllMembers collection
        if AllMembers.TryGetValue(Key, ActiveMember) then
          if ActiveMember <> nil then
            Listbox1.Items.Add(ActiveMember.GetFullname)
        else
          Listbox1.Items.Add('Could not locate TMember with Key: ' + IntToStr(Key));
      finally
        ActiveMember.Free;
      end;
    end;
end;

to

procedure TfrmMainClient.btnRefreshClick(Sender: TObject);
var
  Key : Integer;
begin
  Listbox1.Clear;
  for Key in AllMembers.Keys do
  begin
    Listbox1.Items.Add(AllMembers[Key].GetFullname);
  end;
end;
shyambabu
  • 169
  • 11
  • Aside: constructor must appear before try, otherwise exception in constructor leads to you calling `Free` on an uninitialised reference. – David Heffernan Aug 31 '16 at 10:52
  • 5
    As for the question, there are loads of details missing. The object returned from `GetMember`. Who owns it? Who is in charge of destroying it? What is `AllMembers`? A `TObjectList` with `OwnsObjects` set to `True`. This smells like a question that needs a [mcve]. – David Heffernan Aug 31 '16 at 10:54
  • 1
    How is `Server.GetMemeber()` implemented? As @David said, please provide an MCVE. There's too much speculation otherwise. – Tom Brunberg Aug 31 '16 at 11:15
  • Debug your program. Create an empty overridden destructor for your class. Place a breakpoint on it and run your code. Check the call stack and see when and by whom the destructor is called – Agustin Ortu Aug 31 '16 at 14:14

1 Answers1

-1

Change AllMembers's type from

TObjectList<TMember> 

to

TDictionary<Integer, TMember>

And use below logic to add TMember objects to TDictionary. Then you can use itemindex to fecth the membe from AllMembers list like AllMembers[itemindex].

procedure TfrmMainClient.btnCreateMemClick(Sender: TObject);
var
  Server : TServerMethodsClient;
  newMem : TMember;
  Idx : Integer;
begin
  try
    Server := TServerMethodsClient.Create(DM1.ServerConn.DBXConnection);
    newMem := Server.GetMember(edtFName.Text, '');
    if Assigned(newMem) then begin
      Idx := Listbox1.Items.Add(newMem.GetFullname);
      AllMembers.Add(Idx, newMem);
    end;
  finally
    Server.Free;
  end;
end;
shyambabu
  • 169
  • 11
  • Thank you very much for your assistance. It works, but I do encounter AVs still when I try to retrospectively iterate over the TDictionary to read back the objects it currently contains. I tried using its own iterator (TPair), but accessing the TMember Key.Value portion of it still causes an AV. How can I best do that? Or, should I shy away from full objects and rather go for records instead? – Hendrik Potgieter Sep 01 '16 at 08:07
  • Could you add the code to your question how you are looping the list. And how you are creating the list? when you freeing the list? – shyambabu Sep 01 '16 at 08:24
  • They don't allow me to put the code here, so I will try to put it elsewhere (no idea where, though)... – Hendrik Potgieter Sep 01 '16 at 09:26