Delphi 10 Seattle Datasnap error: "Operation failed. Connection was closed."

Delphi 10 Seattle Datasnap error: "Operation failed. Connection was closed."

我使用向导创建了一个独立的 Datasnap TCP/IP 服务器。我选择了示例方法(echostring 和 reversestring)。我保存了服务器并 运行 它。然后我创建了一个客户端应用程序,并使用 file-new-other 向该客户端项目添加了一个 ClientModule 以及 ClientClasses 单元。在主窗体上。我添加了一个按钮。在按钮的 onclick 事件处理程序上,我添加了以下代码:

procedure TForm1.Button1Click(Sender: TObject);
begin
  if ClientModule1.SQLConnection1.Connected then
  begin
    Button1.Text := 'Open';
    ClientModule1.SQLConnection1.Close;
  end
  else
  begin
    Button1.Text := 'Close';
    // ClientModule1.SQLConnection1.Open;
    ClientModule1.ServerMethods1Client.ReverseString('myteststring');
  end;
end;

这里的目的是模拟客户端定期登录和退出服务器而不是保持连接的情况。这对于部署到移动设备的应用程序尤其重要。

你可以看到我把Connection.Open注释掉了,因为第一次调用ServerMethods1client会打开连接。生成的代码如下所示:

function TClientModule1.GetServerMethods1Client: TServerMethods1Client;
begin
  if FServerMethods1Client = nil then
  begin
    SQLConnection1.Open;
    FServerMethods1Client := TServerMethods1Client.Create(SQLConnection1.DBXConnection, FInstanceOwner);
  end;
  Result := FServerMethods1Client;
end;

现在问题来了。第一次点击按钮时,连接打开,方法被调用。第二次单击该按钮时,连接将关闭。 在第 3 次单击时,TDBXCommand 代码引发了异常 "Operation Failed. Connection was Closed"。

作为解决方法,我试过这个:

procedure TForm1.Button1Click(Sender: TObject);
begin
  if ClientModule1.SQLConnection1.Connected then
  begin
    Button1.Text := 'Open';
    ClientModule1.SQLConnection1.Close;
    ClientModule1.ServerMethods1Client := nil;
  end
  else
  begin
    Button1.Text := 'Close';
    // ClientModule1.SQLConnection1.Open;
    ClientModule1.ServerMethods1Client.ReverseString('myteststring');
  end;
end;

这确实解决了问题,因为 ClientModule1 的 FServerMethods1Client 实例已重置,因此创建代码 运行s 再次像在第一个 运行 上所做的那样。

现在唯一的其他问题是(我正在使用 Eurekalog)它会造成内存泄漏。

我做错了什么?在不重新启动应用程序的情况下从 Datasnap 服务器重复 connected/disconnect 的正确方法是什么?

第一个错误的原因是绑定客户端代理(允许调用服务器方法)的代码绑定到本地 SQL 连接。注意创建代理的调用 class:

FServerMethods1Client := TServerMethods1Client.Create(SQLConnection1.DBXConnection, ...)

底层 DBExpress 连接通过引用传递,代理 class 使用该连接调用服务器。您关闭并重新打开连接,但 ServerMethodsClient1 使用的底层 DBExpress 连接已被破坏。因此,您会收到 "Connection was closed" 异常。 ServerMethodsClient1 正在使用的连接已关闭。您必须像在第二个示例中那样重新创建 ServerMethodsClient1。

我无法回答你的第二个问题,因为我认为它是特定于 ARC 的。对于 VCL DataSnap 应用程序,我会调用 ServerMethodsClient1.Free 而不是将其设置为 nil。根据我对 Delphi 的 ARC 实现(全部来自新闻组)的非常非常有限的理解,我相信你应该调用 ServerMethodsClient1.DisposeOf,因为 class 是 TComponent[= 的后裔11=]

但我不确定。我敢肯定有人会跳到这里了解 ARC 和销毁对象而不是内存泄漏的正确解决方案。

在我的 Android FMX 实现中,我只调用 servermethods 来完成任务。 (即我不使用 Datasnap 数据组件)。 Datasnap 架构有太多不受控制的数据传输开销,无法在移动设备上实际考虑任何其他事情......为了解决它(并且没有内存泄漏),我现在在需要时创建 TServermethods1Client 的本地实例并且在上下文中释放它们:

function TClientModule1.PostTheLog: Boolean;
var
  Server: TServerMethods1Client;
begin

  Server := TServerMethods1Client.Create(ClientModule1.SQLConnection1.DBXConnection);
  try
      UserID := Server.GetUserID; 
      ...
  finally
      Server.Free;
  end;
end;

现在 ClientModule1.SQLConnection1 可以随意连接和断开连接(最好是在任何调用服务器方法之前连接,然后断开连接)并且不会出现进一步的问题。

这引出了一个问题:可公开访问的 ServerMethods1Client 在哪个理想世界中真正有用?