Возврат двух наборов данных с сервера DataSnap за один запрос - PullRequest
0 голосов
/ 28 августа 2011

2012-06-27 Комментарий

Original Post содержит некоторый полезный код, но на самом деле не иллюстрирует, как вернуть несколько наборов данных с сервера DataSnap в одном запросе от клиентского приложения. Чтобы увидеть пример того, как это сделать, посмотрите на Ответ , помеченный как Правильный в самом низу страницы.


2011-08-31 Комментарий

Благодаря Ганни, я снова посмотрел на все. Проблема была моей собственной ошибкой, которая теперь исправлена. Я могу выполнить несколько операторов SQL на сервере DataSnap в рамках одного запроса клиента к серверу, создав / уничтожив компонент TSQLQuery между каждым запросом к базе данных.

Моя проблема возникла, когда я оставил строку отладки кода в своем хранимом процессе при попытке обойти известную проблему, которая не позволяет получить доступ к параметру output после вызова TSQLStoredProc.Open (http://qc.embarcadero.com/wc/qcmain.aspx?d=90211) .

Таким образом, даже если моя проблема решена, оригинальные проблемы остаются - вы не можете вызвать метод Open, чтобы получить данные и затем получить доступ к параметру output, и вы не можете получить доступ к нескольким наборам данных возвращается из одного сохраненного процесса.

Еще раз спасибо Gunny, за ваше предложение.


Оригинальный пост

Я пытаюсь вернуть два разных набора данных с сервера 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 и наблюдать, как обе команды попадают в базу данных, но первый набор результатов отображается как набор данных для обоих запросов.

TWO : Сделайте то же самое, что и # 1, но используйте TSQLStoredProcedure вместо TSQLQuery. Результат тот же.

THREE : используйте TSQLStoredProcedure и верните оба набора данных из одной и той же хранимой процедуры, например:

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

Так как TSQLStoredProcedure имеет NextRecordSet, я надеялся получить доступ к обоим наборам данных, но без радости. Когда я звоню NextRecordSet, он возвращает nil.

FOUR : Вернуть два значения в одном вызове 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 и есть один входной / выходной параметр с именем value. Я не могу получить доступ к параметру output, когда набор данных также возвращается. Это ИЗВЕСТНАЯ ошибка (уже много лет): http://qc.embarcadero.com/wc/qcmain.aspx?d=90211

ВЫВОД:

Поскольку сервер DataSnap совместно использует свой основной TSQLConnection со всеми подключающимися клиентами, и поскольку компоненты TSQLQuery (или TSQLStoredProc) и TClientDataSet создаются / освобождаются при каждом запросе, остается только одно который может удерживать предыдущий набор данных и возвращать его компонентам TSQLQuery и TSQLStoredProc - это компонент 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

И во время выполнения я делаю несколько вещей, чтобы мне не пришлось развертывать файл .INI для драйверов DBX.Во-первых, модуль, который позволяет мне зарегистрировать мой собственный драйвер без 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 (sqlcon) во время выполнения (при запуске сервера DataSnap):

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?

Любая помощь будет принята с благодарностью.Как вы можете сказать, это сводит меня с ума!

Спасибо, Джеймс

Ответы [ 2 ]

1 голос
/ 28 августа 2011

Что произойдет, если вы закроете 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;

0 голосов
/ 30 августа 2011

Как уже упоминалось, пытаясь обойти две ошибки DBX Framework, я представил ошибку, из-за которой казалось, что TSQLConnection возвращал предыдущий набор данных для последующего запроса данных. Как только я исправил свою ошибку, мне просто пришлось обойти две ошибки DBX Framework (так как мы не можем исправить / перекомпилировать фреймворк самостоятельно):

ONE : Вы не можете вызвать метод Open и получить доступ к параметру output.

TWO : Вы не можете получить доступ к нескольким наборам данных, возвращенным из одного сохраненного процесса.

Обходной путь : я просто выполняю два запроса с сервера 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;

(Я набрал этот пример, не тестируя его. Мой реальный код включает проверку границ вариантного массива и другие динамические элементы.)

...