0

2012-06-27 发表评论

原始帖子有一些有用的代码,但并没有真正说明如何在来自客户端应用程序的一个请求中从 DataSnap 服务器返回多个数据集。要查看如何执行此操作的示例,请查看页面底部标记为正确的答案。


2011-08-31 评论

多亏了 Gunny,我又看了一遍。令人烦恼的问题是我自己的错误,现在已修复。通过在每个数据库查询之间创建/销毁组件,我可以在单个客户端到服务器请求中在 DataSnap 服务器上执行多个 SQL 语句。TSQLQuery

当我尝试解决一个众所周知的问题时,我在存储过程中留下了一行调试代码,该问题阻止您output在调用后访问参数TSQLStoredProc.Openhttp://qc.embarcadero.com/wc/qcmain.aspx? d=90211 )。

因此,即使我的问题解决了,原来的问题仍然存在——您不能调用该Open方法来提取数据然后访问output参数,并且您不能访问从单个存储过程返回的多个数据集。

再次感谢甘尼,您的建议。


原帖

我试图在一个请求中从 DataSnap 服务器返回两个不同的数据集。两者都来自同一个数据库。一种是单字段/单记录值,另一种是多字段/多记录数据集。

DataSnap 服务器有以下方法:

function TDSSvrMethods.GetData(const SQL: string; var Params: OleVariant; var Key: string): OleVariant;
var qry: TSQLQuery; cds: TClientDataSet;
begin
  // create TSQLQuery & TClientDataSet
  // Link the two components via cds.SetProvider(qry);
  // run first query, set 'Key' to the result <-- this works
  qry.Close;
  // run second query <-- I see this hit the database
  // return dataset via 'Result := cds.Data;'
  // destory TSQLQuery & TClientDataSet
end;

这行不通。即使我可以看到两个单独的请求都命中了数据库,但我无法访问第二个结果集。当我尝试时,(再次)返回第一个结果集而不是第二个结果集。

在我创建/销毁查询组件(每个客户端到服务器请求)之前,所有后续客户端到服务器请求都将返回第一个数据集。非常令人沮丧。创建/销毁查询组件解决了这个问题,但现在我在一个客户端执行多个查询到服务器请求,问题又回来了——即使执行新查询,也会返回第一个数据集。

我尝试了几种方法:

ONE:为第一个请求动态创建TSQLQuery组件,拉取 db 值,销毁TSQLQuery,创建一个新的TSQLQuery并拉取第二个数据集。那没有帮助。我可以使用SQL Server Profiler并观察两个命令都命中数据库,但第一个结果集显示为两个查询的数据集。

:做与#1相同,但使用TSQLStoredProcedure而不是TSQLQuery。结果是一样的。

:使用 aTSQLStoredProcedure并从同一个存储过程中返回两个数据集,如下所示:

create procedure sp_test_two_datasets
as
  select 'dataset1' as [firstdataset]
  select * from sometable -- 2nd dataset
go

自从TSQLStoredProcedure有了NextRecordSet,我曾希望访问这两个数据集,但没有乐趣。当我打电话时NextRecordSet,它会返回nil

:在一次调用中返回两个值以TSQLStoredProcedure使用数据集和output参数:

create procedure sp_another_test
  @singlevalue varchar(255) output
as
  select * from sometable
go

Delphi 代码如下所示:

var sp: TSQLStoredProc; cds: TClientDataSet;
...
cds.SetProvider(sp);
...
sp.CommandText := 'sp_another_test :value output';
sp.Params.ParamByName('value').Value := Key; // in/out method parameter from above
sp.Open;
Key := sp.Params.ParamByName('value').Value; // single string value
Result := cds.Data; // dataset
...

我检查 sp.Params 并且有一个名为 的输入/输出参数valueoutput当还返回数据集时,我无法访问该参数。这是一个已知的错误(多年来):http: //qc.embarcadero.com/wc/qcmain.aspx ?d=90211

结论:

由于 DataSnap 服务器TSQLConnection与所有连接的客户端共享其主服务器,并且因为TSQLQuery(or TSQLStoredProc) 和TClientDataSet组件都是随每个请求创建/释放的,所以剩下的唯一可能是保留先前的数据集并将其返回到TSQLQueryandTSQLStoredProc组件是TSQLConnection组件。我尝试TSQLConnection.CloseDataSets在关闭和释放TSQLQuery(或TStoredProc)组件之前调用,但这也无济于事。

也许仔细研究TSQLConnection会有所帮助。以下是它在.dfm文件中的外观:

object sqlcon: TSQLConnection
  DriverName = 'MSSQL'
  GetDriverFunc = 'getSQLDriverMSSQL'
  LibraryName = 'dbxmss.dll'
  LoginPrompt = False
  Params.Strings = (
    'SchemaOverride=%.dbo'
    'DriverUnit=DBXMSSQL'

      'DriverPackageLoader=TDBXDynalinkDriverLoader,DBXCommonDriver150.' +
      'bpl'

      'DriverAssemblyLoader=Borland.Data.TDBXDynalinkDriverLoader,Borla' +
      'nd.Data.DbxCommonDriver,Version=15.0.0.0,Culture=neutral,PublicK' +
      'eyToken=91d62ebb5b0d1b1b'

      'MetaDataPackageLoader=TDBXMsSqlMetaDataCommandFactory,DbxMSSQLDr' +
      'iver150.bpl'

      'MetaDataAssemblyLoader=Borland.Data.TDBXMsSqlMetaDataCommandFact' +
      'ory,Borland.Data.DbxMSSQLDriver,Version=15.0.0.0,Culture=neutral' +
      ',PublicKeyToken=91d62ebb5b0d1b1b'
    'GetDriverFunc=getSQLDriverMSSQL'
    'LibraryName=dbxmss.dll'
    'VendorLib=sqlncli10.dll'
    'HostName=localhost'
    'Database=Database Name'
    'MaxBlobSize=-1'
    'LocaleCode=0000'
    'IsolationLevel=ReadCommitted'
    'OSAuthentication=False'
    'PrepareSQL=True'
    'User_Name=user'
    'Password=password'
    'BlobSize=-1'
    'ErrorResourceFile='
    'OS Authentication=False'
    'Prepare SQL=False')
  VendorLib = 'sqlncli10.dll'
  Left = 352
  Top = 120
end

在运行时,我做了一些事情,这样我就不必为 DBX 驱动程序部署 .INI 文件。首先,让我注册自己的无 INI 驱动程序的单元:

unit DBXRegDB;

interface

implementation

uses
  DBXCommon, DBXDynalinkNative;

type
  TDBXInternalDriver = class(TDBXDynalinkDriverNative)
  public
    constructor Create(DriverDef: TDBXDriverDef); override;
  end;

  TDBXInternalProperties = class(TDBXProperties)
  private
  public
    constructor Create(DBXContext: TDBXContext); override;
  end;

{ TDBXInternalDriver }

constructor TDBXInternalDriver.Create(DriverDef: TDBXDriverDef);
begin
  inherited Create(DriverDef, TDBXDynalinkDriverLoader);
  InitDriverProperties(TDBXInternalProperties.Create(DriverDef.FDBXContext));
end;

{ TDBXInternalProperties }

constructor TDBXInternalProperties.Create(DBXContext: TDBXContext);
begin
  inherited Create(DBXContext);

  Values[TDBXPropertyNames.SchemaOverride]         :=       '%.dbo';
  Values[TDBXPropertyNames.DriverUnit]             :=       'DBXMSSQL';
  Values[TDBXPropertyNames.DriverPackageLoader]    :=       'TDBXDynalinkDriverLoader,DBXCommonDriver150.bpl';
  Values[TDBXPropertyNames.DriverAssemblyLoader]   :=       'Borland.Data.TDBXDynalinkDriverLoader,Borland.Data.DbxCommonDriver,Version=15.0.0.0,Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b';
  Values[TDBXPropertyNames.MetaDataPackageLoader]  :=       'TDBXMsSqlMetaDataCommandFactory,DbxMSSQLDriver150.bpl';
  Values[TDBXPropertyNames.MetaDataAssemblyLoader] :=       'Borland.Data.TDBXMsSqlMetaDataCommandFactory,Borland.Data.DbxMSSQLDriver,Version=15.0.0.0,Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b';
  Values[TDBXPropertyNames.GetDriverFunc]          :=       'getSQLDriverMSSQL';
  Values[TDBXPropertyNames.LibraryName]            :=       'dbxmss.dll';
  Values[TDBXPropertyNames.VendorLib]              :=       'sqlncli10.dll';
  Values[TDBXPropertyNames.HostName]               :=       'ServerName';
  Values[TDBXPropertyNames.Database]               :=       'Database Name';
  Values[TDBXPropertyNames.MaxBlobSize]            :=       '-1';
  Values['LocaleCode']                             :=       '0000';
  Values[TDBXPropertyNames.IsolationLevel]         :=       'ReadCommitted';
  Values['OSAuthentication']                       :=       'False';
  Values['PrepareSQL']                             :=       'True';
  Values[TDBXPropertyNames.UserName]               :=       'user';
  Values[TDBXPropertyNames.Password]               :=       'password';
  Values['BlobSize']                               :=       '-1';
  Values[TDBXPropertyNames.ErrorResourceFile]      :=       '';
  Values['OS Authentication']                      :=       'False';
  Values['Prepare SQL']                            :=       'True';
  Values[TDBXPropertyNames.ConnectTimeout]         :=       '30';

  // Not adding connection pooling to the default driver parameters
end;

var
  InternalConnectionFactory: TDBXMemoryConnectionFactory;

initialization
  TDBXDriverRegistry.RegisterDriverClass('MSSQL_NoINI', TDBXInternalDriver);
  InternalConnectionFactory := TDBXMemoryConnectionFactory.Create;
  InternalConnectionFactory.Open;
  TDBXConnectionFactory.SetConnectionFactory(InternalConnectionFactory);

end.

上述方法包含在项目(.dpr 文件)中,并自行注册驱动程序。下一个方法利用它TSQLConnection在运行时(DataSnap 服务器启动时)设置 (sqlcon):

procedure SetupConnection(const hostname, port, dbname, username, password, maxcon: string);
begin
  if sqlcon.Connected then
    Exit;

  // Our custom driver -- does not use DBXDrivers.ini
  sqlcon.Params.Clear;
  sqlcon.DriverName := 'MSSQL_NoINI';
  sqlcon.VendorLib := sqlcon.Params.Values[TDBXPropertyNames.VendorLib];
  sqlcon.LibraryName := sqlcon.Params.Values[TDBXPropertyNames.LibraryName];
  sqlcon.GetDriverFunc := sqlcon.Params.Values[TDBXPropertyNames.GetDriverFunc];

  sqlcon.Params.Values[TDBXPropertyNames.HostName]           := hostname;
  sqlcon.Params.Values[TDBXPropertyNames.Port]               := port;
  sqlcon.Params.Values[TDBXPropertyNames.Database]           := dbname;
  sqlcon.Params.Values[TDBXPropertyNames.UserName]           := username;
  sqlcon.Params.Values[TDBXPropertyNames.Password]           := password;
  sqlcon.Params.Values[TDBXPropertyNames.DelegateConnection] := DBXPool.sDriverName;
  sqlcon.Params.Values[DBXPool.sDriverName + '.' + TDBXPoolPropertyNames.MaxConnections]    := maxcon;
  sqlcon.Params.Values[DBXPool.sDriverName + '.' + TDBXPoolPropertyNames.MinConnections]    := '1';
  sqlcon.Params.Values[DBXPool.sDriverName + '.' + TDBXPoolPropertyNames.ConnectTimeout]    := '1000';
  sqlcon.Params.Values[DBXPool.sDriverName + '.' + 'DriverUnit']                            := DBXPool.sDriverName;
  sqlcon.Params.Values[DBXPool.sDriverName + '.' + 'DelegateDriver']                        := 'True';
  sqlcon.Params.Values[DBXPool.sDriverName + '.' + 'DriverName']                            := DBXPool.sDriverName;
end;

这些设置中的任何一个是否可能会弄乱TSQLConnection组件并使其缓存数据集并返回它们而不是最近TSQLQuery执行的组件?

任何帮助将不胜感激。正如你所知道的,这让我发疯了!

谢谢,詹姆斯

4

2 回答 2

1

如果你也关闭 CDS 会发生什么?

function TDSSvrMethods.GetData(const SQL: string; var Params: OleVariant; var Key: string): OleVariant; 
var qry: TSQLQuery; cds: TClientDataSet; 
begin 
  // create TSQLQuery & TClientDataSet
  // Link the two components via cds.SetProvider(qry);
  // run first query, set 'Key' to the result <-- this works
  qry.Close;
  cds.Close;
  // run second query <-- I see this hit the database
  cds.Open
  // return dataset via 'Result := cds.Data;'
  // destory TSQLQuery & TClientDataSet end;

于 2011-08-27T21:33:19.673 回答
0

如前所述,在尝试解决两个 DBX 框架错误时,我引入了一个错误,它使 TSQLConnection 看起来像是在为后续数据请求交回先前的数据集。一旦我修复了我的错误,我只需要解决两个 DBX 框架错误(因为我们不能自己修复/重新编译框架):

:您不能调用该Open方法并访问output参数。

:您无法访问从单个存储过程返回的多个数据集。

解决方法:我只需执行两个从 DataSnap 服务器到数据库的查询,然后处理/打包各个数据集以发送回客户端(在一个响应中)。


2012-06-27 发表评论

由于这个线程有几个视图,我想我会解释如何将多个数据集打包成来自 DataSnap 服务器的单个响应。

DataSnap 可以OleVariant向客户端应用程序返回一个。OleVariant创建一个数组很容易OleVariant。由于TClientDataSet.Data属性是OleVariant,我们可以创建一个数据集数组以传回客户端。此示例返回 5 个数据集。假设 DataSnap 服务器中存在这些方法:

function TServerMethods1.GetData(SQL: string): OleVariant;
var cds: TClientDataSet;
begin
  cds := TClientDataSet.Create(nil);
  try

    { setup 'cds' to connect to database }
    { pull data }

    Result := cds.Data;
  finally
    FreeAndNil(cds);
  end;
end;

function TServerMethods1.GetMultipleDataSets: OleVariant;
begin
  Result := VarArrayCreate([0, 4], varVariant);
  Result[0] := GetData('select * from Table1');
  Result[1] := GetData('select * from Table2');
  Result[2] := GetData('select * from Table3');
  Result[3] := GetData('select * from Table4');
  Result[4] := GetData('select * from Table5');
end;

您可以通过在表单上放置 5 个TClientDataSet组件并将它们的Data属性分配给OleVariant.

procedure X;
var DataArray: OleVariant;
begin
  try
    with ProxyMethods.TServerMethods1.Create(SQLConnection1.DBXConnection, True) do
    try
      DataArray := GetMultipleDataSets;
    finally
      Free;
    end;

    ClientDataSet1.Data := DataArray[0];
    ClientDataSet2.Data := DataArray[1];
    ClientDataSet3.Data := DataArray[2];
    ClientDataSet4.Data := DataArray[3];
    ClientDataSet5.Data := DataArray[4];
  finally
    VarClear(DataArray);
  end;
end;

(我在没有测试的情况下输入了这个示例。我的实际代码包括变量数组边界检查和其他动态元素。)

于 2011-08-29T22:42:53.743 回答