Devolver dos conjuntos de datos del servidor DataSnap en una sola solicitud

2012-06-27 Comentario

Al Mensaje original tiene un código útil, pero realmente no ilustra cómo devolver múltiples conjuntos de datos de un servidor DataSnap en una solicitud de una aplicación cliente. Para ver un ejemplo de cómo hacer eso, mire el Responder marcado como Correcto en la parte inferior de la página.


2011-08-31 Comentario

Gracias a Gunny, miré todo de nuevo. El problema molesto fue mi propio error, que ahora está arreglado. I enlatado ejecutar múltiples sentencias SQL en el servidor DataSnap dentro de un solo cliente a la solicitud del servidor creando / destruyendo el TSQLQuery componente entre cada consulta de la base de datos.

Mi problema ocurrió cuando dejé una línea de depuración de código en mi proceso almacenado mientras intentaba solucionar un problema bien conocido que le impide acceder a un output parámetro después de llamar TSQLStoredProc.Open ( http://qc.embarcadero.com/wc/qcmain.aspx?d=90211 ).

Entonces, aunque mi problema está resuelto, los problemas originales permanecen: no puedes llamar al Open método para extraer datos y luego acceder a un output param, y no puede acceder a varios conjuntos de datos devueltos desde un solo proceso almacenado.

Gracias de nuevo, Gunny, por tu sugerencia.


Mensaje original

Estoy tratando de devolver dos conjuntos de datos diferentes de un servidor DataSnap en una solicitud. Ambos provienen de la misma base de datos. Uno es un valor de campo único / registro único, el otro es un conjunto de datos de campos múltiples / registros múltiples.

El servidor DataSnap tiene el siguiente método:

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;

Esto no funciona. Aunque puedo ver que ambas solicitudes individuales llegan a la base de datos, no puedo acceder al segundo conjunto de resultados. Cuando lo intento, se devuelve el primer conjunto de resultados (de nuevo) en lugar del segundo conjunto de resultados.

Antes de crear / destruir los componentes de la consulta (con cada solicitud de cliente a servidor), todas las solicitudes posteriores de cliente a servidor devolverían el primer conjunto de datos. Muy frustrante. La creación / destrucción de los componentes de la consulta solucionó ese problema, pero ahora que ejecuto varias consultas en una solicitud de cliente a servidor, el problema ha regresado: el primer conjunto de datos se devuelve incluso cuando se ejecuta una nueva consulta.

He probado varios enfoques:

Y UNA : Crea dinámicamente el TSQLQuery componente para la primera solicitud, extraiga el valor db, destruya el TSQLQuery, crear un nuevo TSQLQuery y extraiga el segundo conjunto de datos. Eso no ayudó. Puedo usar Analizador de SQL Server y observe que ambos comandos llegan a la base de datos, pero el primer conjunto de resultados aparece como el conjunto de datos para ambas consultas.

DOS : Haga lo mismo que el n. ° 1, pero use TSQLStoredProcedure en lugar de TSQLQuery. El resultado es el mismo.

TRES : Utilizar una TSQLStoredProcedure y devolver ambos conjuntos de datos desde el mismo procedimiento almacenado, así:

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

Como TSQLStoredProcedure tiene un NextRecordSet, Esperaba acceder a ambos conjuntos de datos, pero no me alegro. Cuando llamo NextRecordSet, vuelve nil.

CUATRO : Devuelve los dos valores en una llamada a TSQLStoredProcedure usando un conjunto de datos y un output parámetro:

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

El código Delphi se parece a esto:

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
...

Inspecciono sp.Params y hay un parámetro de entrada / salida llamado value. No puedo acceder al output parámetro cuando también se devuelve un conjunto de datos. Este es un error CONOCIDO (desde hace muchos años): http://qc.embarcadero.com/wc/qcmain.aspx?d=90211

CONCLUSIÓN:

Dado que el servidor DataSnap comparte su principal TSQLConnection con todos los clientes conectados, y porque el TSQLQuery (o TSQLStoredProc) y el TClientDataSet Todos los componentes se crean / liberan con cada solicitud, lo único que queda que podría ser aferrarse al conjunto de datos anterior y devolverlo al TSQLQuery y TSQLStoredProc componentes es el TSQLConnection componente. Intenté llamar TSQLConnection.CloseDataSets antes de cerrar y liberar el TSQLQuery (o TStoredProc) componentes, pero eso tampoco ayudó.

Quizás una mirada más cercana a TSQLConnection ayudará. Así es como se ve en el .dfm archivo:

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

Y en tiempo de ejecución, hago algunas cosas para no tener que implementar el archivo .INI para los controladores DBX. Primero, la unidad que me permite registrar mi propio controlador sin 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.

El método anterior se incluye en el proyecto (archivo .dpr) y registra automáticamente el controlador. El siguiente método lo utiliza para configurar el TSQLConnection (sqlcon) en tiempo de ejecución (cuando se inicia el servidor 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;

¿Alguna de estas configuraciones quizás está arruinando el TSQLConnection componente y haciéndolo almacenar en caché conjuntos de datos y devolverlos en lugar del que el más reciente TSQLQuery componente ejecutado?

Cualquier ayuda será muy apreciada. Como puede ver, ¡esto me está volviendo loco!

Gracias James

preguntado el 27 de agosto de 11 a las 20:08

No he usado Delphi en años, pero sospecho que el problema está en los cds. Si mezcla el orden de las dos declaraciones, ¿siempre obtiene los resultados de la primera declaración? ¿Existe un método cds.clear explícito al que pueda llamar entre consultas? -

Buena pregunta ... Resulta que al tratar de solucionar otros errores de Embarcadero existentes, introduje mi propio error, lo que hizo que pareciera que estaba almacenando en caché el primer conjunto de resultados. Gracias por tomarse el tiempo para responder. -

Me alegra que hayas encontrado una resolución. Hace poco compré Delphi después de una pausa de 7-8 años. Pasé de D5E a D2010 Pro. Obtuve todos los libros de Marco Cantu, así que tengo una pista de auditoría de lo que cambió en cada versión, pero es difícil. Así que es un gran lugar para pasar el rato. -

XE2 se lanzará en cualquier momento (la versión más reciente de Delphi). Se jacta de tener un compilador de 64 bits, compilación cruzada en Windows y Mac, y herramientas para dispositivos portátiles. La parte más difícil de pasar de D5 a D2009 fue Unicode. Tuve que ocultar todo mi código anterior, lo que me llevó bastante tiempo. -

Tenga en cuenta que QualityCentral ahora se ha cerrado, entonces no puedes acceder qc.embarcadero.com enlaces más. Si necesita acceder a datos antiguos de control de calidad, consulte QCScraper. -

2 Respuestas

¿Qué pasa si cierras el CDS también?

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;

Respondido 28 ago 11, 01:08

Lo probé y no ayudó. SIN EMBARGO, me hizo escudriñar todo y encontré el problema. Lo publicaré como respuesta. Gracias y +1 por tu ayuda. - James L.

Como se mencionó, al tratar de solucionar los dos errores de DBX Framework, presenté un error que hacía que pareciera que TSQLConnection estaba devolviendo un conjunto de datos anterior para una solicitud de datos posterior. Una vez que solucioné mi error, solo tuve que solucionar los dos errores de DBX Framework (ya que no podemos arreglar / recompilar el marco nosotros mismos):

Y UNA : No puedes llamar al Open método y acceder a un output param.

DOS : No puede acceder a varios conjuntos de datos devueltos desde un solo proceso almacenado.

Solución : Simplemente ejecuto dos consultas desde el servidor DataSnap a la base de datos y luego proceso / empaqueto los conjuntos de datos individuales para enviarlos al cliente (en una respuesta).


2012-06-27 Comentario

Dado que este hilo tiene varias vistas, pensé en explicar cómo empaqueto múltiples conjuntos de datos en una sola respuesta desde el servidor DataSnap.

DataSnap puede devolver un OleVariant a la aplicación cliente. Es fácil crear un OleVariant que es una matriz de OleVariant. Desde el TClientDataSet.Data la propiedad es una OleVariant, podemos crear una matriz de conjuntos de datos para devolverlos al cliente. Este ejemplo devuelve 5 conjuntos de datos. Suponiendo que estos métodos existen en el servidor 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;

Puede asignar los datos del lado del cliente colocando 5 TClientDataSet componentes en su formulario y asignando sus Data propiedad a los elementos de la 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;

(Escribí este ejemplo sin probarlo. Mi código real incluye verificación de límites de matriz de variantes y otros elementos dinámicos).

Respondido el 11 de Septiembre de 13 a las 22:09

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.