Комментарий 27 июня 2012 г.
В исходном сообщении есть полезный код, но на самом деле он не иллюстрирует, как вернуть несколько наборов данных с сервера DataSnap в одном запросе из клиентского приложения. Чтобы увидеть пример того, как это сделать, посмотрите на Ответ с пометкой Правильно в самом низу страницы.
2011-08-31 Комментарий
Благодаря Ганни, я снова все посмотрел. Досадной проблемой была моя собственная ошибка, которая теперь исправлена. Я могу выполнять несколько операторов SQL на сервере DataSnap в рамках одного запроса клиента к серверу, создавая/удаляя компонент TSQLQuery
между каждым запросом к базе данных.
Моя проблема возникла, когда я оставил отладочную строку кода в своем хранимом процессе, пытаясь обойти хорошо известную проблему, которая не позволяет вам получить доступ к параметру output
после вызова TSQLStoredProc.Open
( http://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
для первого запроса, извлеките значение базы данных, уничтожьте TSQLQuery
, создайте новый TSQLQuery
и извлеките второй набор данных. Это не помогло. Я могу использовать SQL Server Profiler и наблюдать, как обе команды обращаются к базе данных, но первый результирующий набор отображается как набор данных для обоих запросов.
ДВА: сделайте то же, что и в #1, но используйте TSQLStoredProcedure
вместо TSQLQuery
. Результат тот же.
ТРИ: используйте TSQLStoredProcedure
и возвращайте оба набора данных из одной и той же хранимой процедуры, например:
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 и вижу один параметр ввода/вывода с именем 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
?
Любая помощь будет принята с благодарностью. Как вы понимаете, это сводит меня с ума!
Спасибо, Джеймс
qc.embarcadero.com
ссылкам. Если вам нужен доступ к старым данным контроля качества, посмотрите QCScraper. - person Remy Lebeau   schedule 09.06.2017