Delphi: Clientdataset: EDatabaseError: Missing Data-Package using Synapse

Acron picture Acron · Aug 14, 2009 · Viewed 8.2k times · Source

From the client I am sending a string to the server what he should send me back. This time its a stream created by a ClientDataSet. Unfortunately receiving (or sending??) does not work at the moment.

Note: I am using Synapse with blocking sockets. The server is multithreaded.

The Server:

procedure TTCPSocketThrd.Execute;
var s: String;
    strm: TMemoryStream;
    ADO_QUERY: TADOQuery;
    DS_PROV: TDataSetProvider;
    DS_CLIENT: TClientDataSet;
begin
    CoInitialize(nil);
    Sock := TTCPBlockSocket.Create;
  try
    Sock.Socket := CSock;
    Sock.GetSins;
    with Sock do
        begin
        repeat
        if terminated then break;
          //if within 60s no data is input, close connection.
          s := RecvTerminated(60000,'|');
          if s = 'getBENUds' then
            begin
              //ini ADO_QUERY
            ADO_QUERY := TADOQuery.Create(Form1);                    
                ADO_QUERY.ConnectionString := 'private :)';
                ADO_QUERY.SQL.Clear;
                ADO_QUERY.SQL.Add('sql_query');
                ADO_QUERY.Open;
              //ini DS_PROV
                DS_PROV := TDataSetProvider.Create(ADO_QUERY);
                DS_PROV.DataSet := ADO_QUERY;
              //ini DS_CLIENT
                DS_CLIENT := TClientDataSet.Create(ADO_QUERY);
                DS_CLIENT.ProviderName := 'DS_PROV';
                DS_CLIENT.SetProvider(DS_PROV);
              //DSCLIENTDATASET bauen
                strm := TMemoryStream.Create;;
                DS_CLIENT.Open;

                DS_CLIENT.SaveToStream(strm);
                SendStream(strm);
             end;

The Client:

procedure TForm1.btnConnectClick(Sender: TObject);
begin
  CSocket := TTCPBlockSocket.Create;
  strmReply := TMemoryStream.Create;
  ClientDataSet1 := TClientDataSet.Create(Form1);
    try
    CSocket.Connect('ip', 'port');
    if CSocket.LastError = 0 then
    begin
      //Sending to the server I want data
      CSocket.SendString('getBENUds|');
      if CSocket.LastError = 0 then
        begin
            //Waiting for data
            //Receiving the Stream in strmReply, 5000ms timeout
            CSocket.RecvStream(strmReply, 5000);
            //Loading the data into ClientDataSet
            ClientDataSet1.LoadFromStream(strmReply);
            ClientDataSet1.Open;
        end;
    end;
  except
    on E:Exception do
        ShowMessage(E.Message);
    end;
  CSocket.Free;
end;

Now, whenever I start the server and click the connect button on the client, it SHOULD transfer the DataSet to the client and the TDBGrid SHOULD come to life.

Unfortunately this does not happen. Instead I get the error as stated in the title: Missing data provider or datapackage. But the data provider is set to DataSetProvider1 (in object inspector).

How can I make it work that the client TDBGrid is filled with data?

Answer

Jeroen Wiert Pluimers picture Jeroen Wiert Pluimers · Aug 14, 2009

You are creating a new instance inside the ClientDataSet1 variable, but none of the other components on your form will reference to that.

That's not the cause of the "Missing data provider or datapackage" error message: in order to find that out you should post a reproducible case.

The easiest way to get that reproducible case going would be to:

  1. Put two TClientDataSets on your server (ClientDataSet1 and ClientDataSet2)
  2. Load the data into that ClientDataSet1 at design time using a provider and such
  3. Save that data from ClientDataSet1 to a .CDS file
  4. Load the .CDS file into ClientDataSet2 at design time
  5. Send over ClientDataSet2 to your client

If it still reproduces, then post that .pas/.dfm somewhere. If it does not reproduce, then start cutting out portions until it reproduces.

Good luck!

--jeroen

Edit: I downloaded your sources and got them working in Delphi 2009.

It contains a few issues, and Synapse library contains an issues as well.

The majority of your issues come down to being not precise. I already had a vague feeling for that, based on your source code formatting, the mix of naming conventions you were using for your stuff, and the lack of freeing allocated objects.

Before I forget: the very first thing I did was making sure that I enabled use debug dcus and disable optimizations in the compiler options. Those are invaluable settings for digging deeper into problems. I didn't add any cleanup to your existing code, as I wanted it to be as close as possible. But I did run a source code formatter so that at least the then blocks were on separate lines.

Lets start with the sending code.

I got rid of your database connection, copied the ClientDataSet1 from the client to the server, and refactored your SServer unit to that both TTCPSocketDaemon and TTCPSocketThrd now have access to FClientDataSet: TClientDataSet

That got me a kind of reproducible case that I asked for before.

Then I started running the client. There it appeared that LogThis(strmReply.Size); was outputting 0 (zero!), so it was not receiving anything.

That made me look into the server again. It seems you were splitting the incoming arguments using Delimited text, but after that you were refering to sl.Names[0] in stead of if sl[0]. That fails in Delphi 2009, and probably in other Delphi versions as well. In addition to that, you were not checking for an empty s, so it would fail with an List index out of bounds after each time out.

Then I added some debugging code: I wanted to see what was actually being sent. This caused me to stream using XML in stead of binary using S_CLIENT.SaveToStream(strm, dfXMLUTF8); and to copy the buffer to a file locally, so I could watch that file. The file was OK: it appeared to be a valid XML representation of a ClientDataSet.

Finally, I noticed that you were using SendBuffer in the server, and RecvStream in the client. Those do not match: you have to choose, so either use buffers or streams at both sides of the connection! I chose for streams.

So the sending code becomes this:

procedure TTCPSocketThrd.Execute;
var
  s: string;
  strm: TMemoryStream;
  ADO_QUERY: TADOQuery;
  DS_PROV: TDataSetProvider;
  DS_CLIENT: TClientDataSet;
  sl: TStringList;
  adoconnstr: string;
  CDSStream: TFileStream;
begin
  CoInitialize(nil);
  Sock := TTCPBlockSocket.Create;
  adoconnstr := 'POST YOUR CONNECTION STRING HERE, IF TOO LONG adoconnstr := adoconnstr + ''nwestring''';
  try
    Sock.Socket := CSock;
    sl := TStringList.Create;
    Sock.GetSins;
    with Sock do
    begin
      repeat
        if terminated then
          break;
        s := RecvTerminated(60000, '|');

        if s = '' then
          Exit;

        //Den Text mit Leerzeichen splitten zur besseren Verarbeitung
        sl.Delimiter := ' ';
        sl.DelimitedText := s;

        LogThis(sl.Names[0]);
        if sl[0] = 'getBENUds' then // jpl: not sl.Names[0] !!!!
        begin
          //          //ini ADO_QUERY
          //          ADO_QUERY := TADOQuery.Create(Form1);
          //          ADO_QUERY.ConnectionString := adoconnstr;
          //          ADO_QUERY.SQL.Clear;
          //          ADO_QUERY.SQL.Add('SELECT * FROM BENU');
          //          ADO_QUERY.Open;
          //          LogThis('ADO_QUERY fertig');
          //          //ini DS_PROV
          //          DS_PROV := TDataSetProvider.Create(ADO_QUERY);
          //          DS_PROV.DataSet := ADO_QUERY;
          //          LogThis('DS_DSPROV fertig');
          //          //ini DS_CLIENT
          //          DS_CLIENT := TClientDataSet.Create(ADO_QUERY);
          //          DS_CLIENT.ProviderName := 'DS_PROV';
          //          DS_CLIENT.SetProvider(DS_PROV);
          DS_CLIENT := FClientDataSet;
          LogThis('DS_CLIENT fertig');
          //DSCLIENTDATASET bauen
          strm := TMemoryStream.Create;
          DS_CLIENT.Open;
          //DS_CLIENT.Open;
          DS_CLIENT.SaveToStream(strm, dfXMLUTF8); // jpl: easier debugging than binary
          strm.Seek(0, soBeginning);
          SendStream(strm); // jpl: not SendBuffer(strm.Memory, strm.Size);  !!!

          strm.Seek(0, soBeginning);
          CDSStream := TFileStream.Create('Server.cds', fmCreate);
          CDSStream.CopyFrom(strm, strm.Size);
          CDSStream.Free();

          LogThis('gesendet');
        end
        else if sl[0] = 'validuser' then // jpl: not sl.Names[0] !!!!
        begin
          //do stuff
        end;
        if lastError <> 0 then
          break;
      until false;
      Form1.Memo1.Lines.Add('down');
    end;
  finally
    Sock.Free;
  end;

end;

So I went to revisit the receiving code. Since you were already logging strmReply.Size, I wondered why you didn't react on the special case where it was 0 (zero), so I added it: LogThis('empty stream received')

Then I started debugging, and found out that strmReply.Size was still 0 (zero) every time. So I started digging into the Synapse code (I took the copy I already had that is included in the Habari components) I have been using lately. There I found out two things:

  1. upon sending the stream, it was encoding the length of the stream in the first 4 bytes
  2. the encoding was done in a different byte order than the decoding

This is no doubt a bug in Synapse, so feel free to report it to them. The result is that RecvStream is not the counterpart of SendStream, but RecvStreamIndy is.

After all these changes, it still didn't work. The reason is that ClientDataSet1.LoadFromStream(strmReply); is starting to read from the current position in the stream (you can check that out yourself; the core loading logic is in TCustomClientDataSet.ReadDataPacket). So it seems you forgot adding strmReply.Seek(0, soBeginning);, and after I added that, it suddenly worked.

So the receiving code is this:

procedure TForm1.btnConnectClick(Sender: TObject);
var
  CDSStream: TFileStream;
begin
  CSocket := TTCPBlockSocket.Create;
  strmReply := TMemoryStream.Create;
  ClientDataSet1 := TClientDataSet.Create(Form1);
  try
    //      CSocket.Connect('10.100.105.174', '12345');
    CSocket.Connect('127.0.0.1', '12345');
    if CSocket.LastError = 0 then
    begin
      LogThis('verbunden');
      CSocket.SendString('getBENUds|');
      LogThis('''getBENUds|'' gesendet');
      if CSocket.LastError = 0 then
      begin
        CSocket.RecvStreamIndy(strmReply, 50000); // jpl: this fails, because SendStream defaults to Indy: CSocket.RecvStream(strmReply, 50000);
        LogThis(strmReply.Size);

        if strmReply.Size = 0 then
          LogThis('empty stream received')
        else
        begin
          strmReply.Seek(0, soBeginning);
          CDSStream := TFileStream.Create('Client.cds', fmCreate);
          CDSStream.CopyFrom(strmReply, strmReply.Size);
          CDSStream.Free();

          strmReply.Seek(0, soBeginning); // jpl: always make sure that the stream position is right!
          ClientDataSet1.LoadFromStream(strmReply);
          ClientDataSet1.Open;
        end;
      end;
    end;
  except
    on E: Exception do
      ShowMessage(E.Message);
  end;
  CSocket.Free;
end;

In the end it was not difficult to solve your issues. The hardest part was finding out about RecvStreamIndy, the rest was a mere cleanup of your code and being precise in what happens when: that took most of the time. In this case, stream handling requires you to watch your stream positions carefully.

Regards,

--jeroen