Ir al contenido


Foto

Transacciones Explicitas con Datasnap en multiples capas usando Providers


  • Por favor identifícate para responder
6 respuestas en este tema

#1 nwac77

nwac77

    Member

  • Miembros
  • PipPip
  • 13 mensajes

Escrito 18 mayo 2012 - 03:44

De manera a poder tener una idea general del sistema te comento lo siguiente:

SISTEMA DE BASE DE DATOS RELACIONAL: FIREBIRD 2.5
SIStema Operativo donde corre el FIrebird: LINUX
Estaciones de trabajo y Servidor DataSnap: Corriendo sobre Windows 7 (32 y 64 bits)
Metodo de Conexion: TCP/IP
Version del Delpi: XE2

ESTRUCTURA ACTUAL DEL SISTEMA (SIN CONTROLAR EXPLICITAMENTE LAS TRANSACCIONES)


Aplicación en el Lado del Cliente: (ESTRUCTURA DE LOS OBJETOS DE CONECCION)
Form: DATA MODULE


 [glow=red,2,300]SqlConnection[/glow]      ------->        [glow=red,2,300]DsProviderConnection[/glow]            ---------->      [glow=red,2,300]ClientDataSet[/glow]
  Driver: [glow=red,2,300]Datasnap[/glow]                      ServerClassname: TServerMethods1            RemoteServer: DsProviderConnection
  ConnectionName: SAMSYS        SqlConnection: SAMSYS                                Name: cdsCiudad
                                                                                                                          ProviderName: prvCiudad

[glow=red,2,300]en el evento onNewRecord[/glow]


delphi
  1. procedure TDatos.cdsCiudadNewRecord(DataSet: TDataSet);
  2. begin
  3. Datos.AsignarClavePrimaria(cdsCiudadMin_Id);
  4. end;
  5.  
  6. // Asigna el valor inicial del campo PK, luego este valor sera reemplazado por el valor final
  7. // que ha sido generado. Ojo que el valor que recibe esta funcion es el campo agregated que
  8. // debe estar presente en cada una de las tablas. Ejemplo:
  9. // AsignarClavePrimaria(cdsnacionalidadMinId);
  10. // Recueda que minid = campo agregated y cdsnacionalidad es la tabla.
  11. procedure TDatos.AsignarClavePrimaria(F: TAggregateField);
  12. var
  13.   i: integer;
  14.   v: variant;
  15. begin
  16.   v:= F.Value;
  17.   if (varisnull(v) or (v > 0))  then
  18.       v:= 0;
  19.   for I := 0 to f.DataSet.FieldCount -1 do
  20.     begin
  21.         with f.DataSet.Fields[i] do
  22.             if pfInKey in ProviderFlags then
  23.               begin
  24.                 Value:= v-1;
  25.                 exit;
  26.               end;
  27.     end;
  28. end;







Aplicacion del lado del Cliente:
Form: TF_Ciudades
Button: GRABAR




delphi
  1. procedure TF_Ciudades.BnOkClick(Sender: TObject);
  2. var
  3. bkp: OleVariant;
  4. i: integer;
  5. rd: TCustomClientDataSet;
  6. begin
  7.   inherited;    // respetar esta llamada;
  8.   if datos.cdsCiudadNOM_CIUDAD.IsNull then
  9.     begin
  10.         MessageDlg('No puede dejar en blanco el nombre de la Ciudad !!!',mtwarning,[mbok],0);
  11.         DBEdit1.SetFocus;
  12.         CerrarFormulario:= false;
  13.         exit;
  14.     end;
  15.   if datos.cdsCiudadID_DEPARTAMENTO.IsNull then
  16.     begin
  17.         MessageDlg('No puede dejar de seleccionar el Departamento donde se localiza!!!',mtwarning,[mbok],0);
  18.         Btn_SelDepto.SetFocus;
  19.         CerrarFormulario:= false;
  20.         exit;
  21.     end;
  22. Datos.cdsCiudad.CheckBrowseMode;
  23. bkp:= Datos.cdsCiudad.data;
  24. if datos.cdsCiudad.ApplyUpdates(0) > 0  then
  25.     begin
  26.       if ultima_accion = dsInsert then
  27.         begin
  28.           // si se estaba agregando y no se pudo grabar el registro, entonces cierra
  29.           // el formulario pq no puedo volver a editarlo.
  30.           CerrarFormulario:= not true;
  31.           // limpia el clientdataset
  32.           datos.cdsCiudad.EmptyDataSet;
  33.           datos.cdsCiudad.Insert;
  34.           rd:= TClientDataSet.Create(nil);
  35.           rd.Data:= bkp;
  36.           // actualiza los campos con los valores previamente ingresados que se
  37.           // hizo una copia antes de hacer apply_updates
  38.           if datos.cdsCiudad.State = dsinsert then
  39.               begin
  40.                 for i := 1 to dsBase.DataSet.Fields.Count  do
  41.                     begin
  42.                       dsbase.DataSet.Fields.FieldByNumber(i).Value:= rd.Fields.FieldByNumber(i).Value;
  43.                 end;
  44.               end;
  45.           FreeAndNil(rd);
  46.         end
  47.       else
  48.       begin // si se esta modificando
  49.           CerrarFormulario:= false;    // impide que el form sea cerrado
  50.           Datos.cdsCiudad.Edit;
  51.       end;
  52.       abort;
  53.     end;
  54. end;
  55. ]




Aplicación en el Lado del Servidor
Form: TServerMethods1
[/color


[glow=red,2,300]Sqlconection[/glow]          --------------> [glow=red,2,300] SqlDataSet  [/glow]        --------------------->  [glow=red,2,300]TDatasetProvider[/glow]
  ConnectionName: SAMSYS            Name: qrCiudad                                Name: prvCiudad
  Driver: [glow=red,2,300]FIREBIRD[/glow]                            Sqlconnection: SAMSYS                      DataSet: qrCiudad
                                                        CommandType: ctQuery                    UpdateMode: upWhereChanged
                                                        CommandText:                                 
                                                          SELECT  C.*,D.NOM_DEPARTAMENTO,
                                                          D.IDLEVER_DPTO from ciudades C
                                                          INNER JOIN DEPARTAMENTOS_ATENDIDOS D
                                                          ON C.ID_DEPARTAMENTO = D.ID_DEPARTAMENTO
                                                          where  c.ID_CIUDAD=:ID_CIUDAD
                                                          order by C.NOM_CIUDAD_NORMALIZADA

[glow=red,2,300]en el evento BeforeUpdateRecord del PROVIDER[/glow]


delphi
  1.  
  2. procedure TServerMethods1.prvCiudadBeforeUpdateRecord(Sender: TObject;
  3.   SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;
  4.   var Applied: Boolean);
  5. var
  6. newcode: Integer;
  7. begin
  8.   if UpdateKind = ukInsert then
  9.     begin
  10.       newcode:= NuevoID(1);
  11.       DeltaDS.FieldByName('ID_CIUDAD').NewValue:= newcode;
  12.       qrCiudad.Tag:= newcode;
  13.     end;
  14. end;



[glow=red,2,300]en el evento AfterUpdateRecord del PROVIDER[/glow]


delphi
  1. procedure TServerMethods1.prvCiudadAfterUpdateRecord(Sender: TObject;
  2.   SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind);
  3. begin
  4.   if (SourceDS = qrCiudad) and (UpdateKind <> ukDelete) then
  5.     if Modificado(DeltaDS, 'NOM_CIUDAD') then
  6.         AsociarNombres(DeltaDS.FieldByName('ID_CIUDAD').OldValue,'CIUDADES');
  7. end;



[glow=red,2,300]en el evento BeforeApplyUpdate del PROVIDER[/glow]


delphi
  1. procedure TServerMethods1.prvCiudadBeforeApplyUpdates(Sender: TObject;
  2.   var OwnerData: OleVariant);
  3. begin
  4. qrCiudad.Tag:= -1;
  5. end;



[glow=red,2,300]en el evento AfterApplyUpdates del PROVIDER[/glow]


delphi
  1. procedure TServerMethods1.prvCiudadAfterApplyUpdates(Sender: TObject;
  2.   var OwnerData: OleVariant);
  3. var
  4.   RecsOut: Integer;
  5.   Params, OwnerData1: OleVariant;
  6.   mycommand: string;
  7. begin
  8.   mycommand:= 'SELECT  C.*,D.NOM_DEPARTAMENTO,D.IDLEVER_DPTO from CIUDADES C ';
  9.   mycommand:= mycommand+ 'INNER JOIN DEPARTAMENTOS_ATENDIDOS D ';
  10.   mycommand:= mycommand+ ' ON d.ID_DEPARTAMENTO = c.ID_DEPARTAMENTO ';
  11.   if qrCiudad.Tag > 0 then
  12.     OwnerData := prvCiudades.GetRecords(-1, RecsOut, 0,
  13.       Format(mycommand+' where ID_CIUDAD  = %d', [qrCiudad.Tag]),
  14.       Params, OwnerData1);
  15. end;



 

El codigo citado mas arriba esta funcionando perfectamente incluso con Datasets Anidados (NeestedDataset)

 


Mi intención es manejar explicitamente las transacciones desde el formulario de TF_Ciudades que esta en el lado del Cliente.

Para ello necesitaria hacer algunas modificaciones a fin de que el botón Grabar del Formulario quede con el siguiente código:





delphi
  1. procedure TF_Ciudades.BnOkClick(Sender: TObject);
  2. var
  3. bkp: OleVariant;
  4. i: integer;
  5. rd: TCustomClientDataSet;
  6. DBXTrans: TDBXTransaction
  7. begin
  8.   inherited;    // respetar esta llamada;
  9.   if datos.cdsCiudadNOM_CIUDAD.IsNull then
  10.     begin
  11.         MessageDlg('No puede dejar en blanco el nombre de la Ciudad !!!',mtwarning,[mbok],0);
  12.         DBEdit1.SetFocus;
  13.         CerrarFormulario:= false;
  14.         exit;
  15.     end;
  16.   if datos.cdsCiudadID_DEPARTAMENTO.IsNull then
  17.     begin
  18.         MessageDlg('No puede dejar de seleccionar el Departamento donde se localiza!!!',mtwarning,[mbok],0);
  19.         Btn_SelDepto.SetFocus;
  20.         CerrarFormulario:= false;
  21.         exit;
  22.     end;
  23.  
  24. // linea Agregada para iniciar supuestamente la transaccion
  25. aDBXTrans := SAMSYS.BeginTransaction(TDBXIsolations.ReadCommitted);
  26.  
  27. Datos.cdsCiudad.CheckBrowseMode;
  28. bkp:= Datos.cdsCiudad.data;
  29. if datos.cdsCiudad.ApplyUpdates(0) > 0  then
  30.     begin
  31.  
  32.       // RollBack transaction
  33.       aDBXConn.RollbackFreeAndNil(aDBXTrans);
  34.  
  35.       if ultima_accion = dsInsert then
  36.         begin
  37.           // si se estaba agregando y no se pudo grabar el registro, entonces cierra
  38.           // el formulario pq no puedo volver a editarlo.
  39.           CerrarFormulario:= not true;
  40.           // limpia el clientdataset
  41.           datos.cdsCiudad.EmptyDataSet;
  42.           datos.cdsCiudad.Insert;
  43.           rd:= TClientDataSet.Create(nil);
  44.           rd.Data:= bkp;
  45.           // actualiza los campos con los valores previamente ingresados que se
  46.           // hizo una copia antes de hacer apply_updates
  47.           if datos.cdsCiudad.State = dsinsert then
  48.               begin
  49.                 for i := 1 to dsBase.DataSet.Fields.Count  do
  50.                     begin
  51.                       dsbase.DataSet.Fields.FieldByNumber(i).Value:= rd.Fields.FieldByNumber(i).Value;
  52.                 end;
  53.               end;
  54.           FreeAndNil(rd);
  55.         end
  56.       else
  57.       begin // si se esta modificando
  58.           CerrarFormulario:= false;    // impide que el form sea cerrado
  59.           Datos.cdsCiudad.Edit;
  60.       end;
  61.       abort;
  62.     end;
  63. else
  64.     // Commit transaction
  65.     aDBXConn.CommitFreeAndNil(aDBXTrans);
  66. end;



En la linea 25 supuestamente inicia la transacción (ojo que estamos en el lado del cliente), pero en realidad lo que sucede es que [color=red][glow=red,2,300]NO SE INICIA[/glow] pues el SQLCONECTION que esta en el lado del cliente se conecta al servidor por medio del driver DATASNAP; y DATASNAP no soporta TRANSACCIONES, el que si soporta las transacciones son los servidores FIREBIRD, SQL,Oracle,etc. que estan en el lado del Servidor, al cual no podemos acceder directamente de esta manera.

Andreano Lanusse, publico un ejemplo en el que se utilizaban transacciones explicitas, y utiliza el siguiente código



delphi
  1. var
  2.  
  3.   aConnName: string;
  4.   aDBXConn: TDBXConnection;
  5.   aDBXTrans: TDBXTransaction;
  6.   aCmnd: TDBXCommand;
  7.   aReader: TDBXReader;
  8.   aColCount: integer;
  9.  
  10. begin
  11.  
  12.   aDBXConn := TDBXConnectionFactory.GetConnectionFactory.GetConnection
  13.     ('EMPLOYEE IB', 'sysdba', 'masterkey');
  14.  
  15.   if aDBXConn <> nil then
  16.   begin
  17.  
  18.     // Write the all connection parameters
  19.     Writeln('================= Connection Properties ============');
  20.     Writeln(aDBXConn.ConnectionProperties.Properties.Text);
  21.     Writeln('====================================================');
  22.     Writeln('');
  23.  
  24.     aCmnd := aDBXConn.CreateCommand;
  25.  
  26.     // Start transaction
  27.     aDBXTrans := aDBXConn.BeginTransaction(TDBXIsolations.ReadCommitted);
  28.  
  29.     // Prepare and execute the SQL Statement
  30.     aCmnd.Text := 'SELECT * FROM Country';
  31.     aCmnd.Prepare;
  32.     aReader := aCmnd.ExecuteQuery;
  33.  
  34.     aColCount := aReader.ColumnCount;
  35.     Writeln('Results from Query: ' + aCmnd.Text);
  36.     Writeln('Number of Columns:  ' + IntToStr(aColCount));
  37.  
  38.     while aReader.Next do
  39.     begin
  40.       Writeln(aReader.Value['Country'].GetAnsiString);
  41.     end;
  42.  
  43.     Writeln('====================================================');
  44.     Writeln('');
  45.  
  46.     // Commit transaction
  47.     aDBXConn.CommitFreeAndNil(aDBXTrans);
  48.  
  49.     Readln;
  50.     aReader.Free;
  51.     aCmnd.Free;
  52.     aDBXConn.Free;
  53.  
  54.   end;
  55.  
  56. end.



Como podras ver en la linea Nro. 13 y 14  está el código para hacer la conección al servidor Interbase, el problema es que de esta manera no se utilizan SQLQuery, Providers ni Clientdataset. y cada vez que haria un almacenamiento de los datos crearia una nueva conección.

Hasta ahora he asistido a varios cursos oficiales de Embarcadero en el Brasil, realize curso a distancia con IAN MARTENS del Instituto Intuitive Sightt, participe de muchisimos Webbinar del propio embarcadero, le he escrito a varios expertos en Delphi, incluido Andreano Lanusse, y otros de borland y nadie pudo  :huh: mostrar un simple ejemplo de como utilizar transacciones explicitas con Clientdatasets y Providers, lo cual me hace pensar que embarcadero no desea que utilizemos transacciones explicitas o es que no necesitamos preocuparnos por este tema.

Desde ya Gracias a los que puedan hechar luz sobre tan nebuloso tema


  • 0

#2 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14.448 mensajes
  • LocationMéxico

Escrito 18 mayo 2012 - 04:35


Caramba!!! un tema muy interesante pero que escapa a mi poco conocimiento y mi nulo conocimiento de DataSnap. Espero que haya alguien que pueda ayudarnos.

Por otro lado te doy la bienvenida a delphiaccess, espero que encuentres agradable y productivo este espacio.

Saludos
  • 0

#3 Wilson

Wilson

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.137 mensajes

Escrito 18 mayo 2012 - 05:16

Hola mi hermano, creo que te estás complicando la vida más de la cuenta, recuerda que el proveedor inicia automáticamente una transacción, y sí solo necesitas actualizar la tabla CIUDADES no requieres de transacciones explícitas, con el ApplyUpdates(0) es suficiente. Si en otra parte de la aplicación  necesitas actualizar varias tablas simultaneamente lo mejor es implementar un procedimiento almacenado, basta engancharlo al proveedor y le puedes pasar parámetros desde el cliente  vía TClientDataset o TSQLSErverMethod sin enganchar al proveedor ( todo depende de las necesidades).

Ahora veo que puedes ahorrar mucho código para validar los campos requeridos, observa este sencillo procedimiento, basta compartirlo en el evento OnBeforePost de todos los TClientDataset o TDataSets de la aplicación, pasándole como parámetro el dataset, y validará todos los campos requeridos.



delphi
  1. procedure TDatos.ValidarRequeridos(D: TDataSet);
  2. var
  3. h, K: Integer;
  4. begin
  5.   for K := 0 to D.FieldCount - 1 do
  6.     if D.Fields[K].Required then
  7.       if (D.Fields[K].Text = '') or D.Fields[K].IsNull then
  8.       begin
  9.         D.Fields[K].FocusControl;
  10.         raise Exception.Create('El campo ' + D.Fields[K].DisplayLabel +
  11.           ' debe contener un valor. Recuerde que los campos marcados con ' +
  12.           'un asterísco son obligatorios');
  13.       end;
  14. end;



Saludos
  • 0

#4 Wilson

Wilson

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.137 mensajes

Escrito 18 mayo 2012 - 05:28

Ahora bién una  forma de iniciar y controlar transacciones explícitas en DataSnap después de Delphi 2009 se hace creando un campo privado ( T: TDbXTransaction;) e  interceptando los siguientes eventos del TDatasetProvider:



delphi
  1. uses
  2.   SysUtils, Classes, Provider, DB, SqlExpr;
  3.  
  4. type
  5.   TDataModule1 = class(TDataModule)
  6.     SQLConnection1: TSQLConnection;
  7.     DataSetProvider1: TDataSetProvider;
  8.     procedure DataSetProvider1BeforeApplyUpdates(Sender: TObject;
  9.       var OwnerData: OleVariant);
  10.     procedure DataSetProvider1AfterApplyUpdates(Sender: TObject;
  11.       var OwnerData: OleVariant);
  12.     procedure DataSetProvider1UpdateError(Sender: TObject;
  13.       DataSet: TCustomClientDataSet; E: EUpdateError; UpdateKind: TUpdateKind;
  14.       var Response: TResolverResponse);
  15.   private
  16.   private
  17.     T: TDbXTransaction;
  18.   public
  19.     { Public declarations }
  20.   end;
  21.  
  22. var
  23.   DataModule1: TDataModule1;
  24.  
  25. implementation
  26.  
  27. {$R *.dfm}
  28.  
  29.  
  30. procedure TDataModule1.DataSetProvider1BeforeApplyUpdates(Sender: TObject;
  31.   var OwnerData: OleVariant);
  32. begin
  33.   T := SQLConnection1.BeginTransaction(TDBXIsolations.ReadCommitted);
  34. end;
  35.  
  36. procedure TDataModule1.DataSetProvider1AfterApplyUpdates(Sender: TObject;
  37.   var OwnerData: OleVariant);
  38. begin
  39.   if SQLConnection1.InTransaction then
  40.     SQLConnection1.CommitFreeAndNil(T);
  41. end;
  42.  
  43.  
  44.  
  45. procedure TDataModule1.DataSetProvider1UpdateError(Sender: TObject;
  46.   DataSet: TCustomClientDataSet; E: EUpdateError; UpdateKind: TUpdateKind;
  47.   var Response: TResolverResponse);
  48. begin
  49.   if SQLConnection1.InTransaction then
  50.     SQLConnection1.RollbackFreeAndNil(T);
  51. end;
  52.  
  53. end.



Saludos
  • 0

#5 nwac77

nwac77

    Member

  • Miembros
  • PipPip
  • 13 mensajes

Escrito 19 mayo 2012 - 08:10

(y)Gracias Wilson por tus apreciaciones, tendre encuenta el codigo que mencionas para el tema de las validaciones.
Por otro lado, las transacciones las necesito manejar especificamente para otro tipo de tablas, como por ejemplo para las ventas (que debe actualizar la existencia de productos, el estado de deuda de cliente, rendicionciones de recorridos, etc).

:huh: Ya que mencionas el tema de procedimientos almacenados y triggers, consulto lo siguiente:
En el caso de que NO UTILIZEMOS TRANSACCIONES EXPLICITAS:
supon que ya se esta procesando el registro 15 de un total de 50 registros que componen el detalle de la factura y que por cada vez que procesa un registro ejecuta el procedimiento almacenado que actualiza la existencia del producto en la tabla de stock, y antes de procesar todos los demas registros del detalle de factura surge un problema de cualquier indole (falta de energia, se pierde la comuniciacion con el servidor, etc). 

  • Sera que hara el rollback en el todos los registros previamente ya procesados?
  • Dejara nuevamente las existencias de los productos como estaban anteriormente. (considerando de que se actualizan a travez de stored procedures)

[move]Desde ya muchas gracias por tu atención.[/move]


  • 0

#6 Wilson

Wilson

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.137 mensajes

Escrito 19 mayo 2012 - 08:27

Recuerda que el propio  procedimiento almacenado  se ejecuta en el ámbito de una transacción en Firebird, ahora bien, supongamos que tienes los detalles de un factura en un dataset anidado dentro del dataset que hace de encabezado de la factura (típico en DataSnap), al pertenecer los dos datasets al mismo  TDataSet Provider, cuando este envía el proceso de actualización también lo hace en el ámbito de una transacción, de tal forma que si algo anda mal en el servidor de Firebird, todo será devuelto atrás y será manejado implícita o explícitamente por el evento OnUpdateError del TDatasetProvider, que obviamente hará un rollback sobre la transacción.

Saludos
  • 0

#7 nwac77

nwac77

    Member

  • Miembros
  • PipPip
  • 13 mensajes

Escrito 19 mayo 2012 - 09:22

gracias
  • 0




IP.Board spam blocked by CleanTalk.