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
procedure TDatos.cdsCiudadNewRecord(DataSet: TDataSet); begin Datos.AsignarClavePrimaria(cdsCiudadMin_Id); end; // Asigna el valor inicial del campo PK, luego este valor sera reemplazado por el valor final // que ha sido generado. Ojo que el valor que recibe esta funcion es el campo agregated que // debe estar presente en cada una de las tablas. Ejemplo: // AsignarClavePrimaria(cdsnacionalidadMinId); // Recueda que minid = campo agregated y cdsnacionalidad es la tabla. procedure TDatos.AsignarClavePrimaria(F: TAggregateField); var i: integer; v: variant; begin v:= F.Value; if (varisnull(v) or (v > 0)) then v:= 0; for I := 0 to f.DataSet.FieldCount -1 do begin with f.DataSet.Fields[i] do if pfInKey in ProviderFlags then begin Value:= v-1; exit; end; end; end;
Aplicacion del lado del Cliente:
Form: TF_Ciudades
Button: GRABAR
delphi
procedure TF_Ciudades.BnOkClick(Sender: TObject); var bkp: OleVariant; i: integer; rd: TCustomClientDataSet; begin inherited; // respetar esta llamada; if datos.cdsCiudadNOM_CIUDAD.IsNull then begin MessageDlg('No puede dejar en blanco el nombre de la Ciudad !!!',mtwarning,[mbok],0); DBEdit1.SetFocus; CerrarFormulario:= false; exit; end; if datos.cdsCiudadID_DEPARTAMENTO.IsNull then begin MessageDlg('No puede dejar de seleccionar el Departamento donde se localiza!!!',mtwarning,[mbok],0); Btn_SelDepto.SetFocus; CerrarFormulario:= false; exit; end; Datos.cdsCiudad.CheckBrowseMode; bkp:= Datos.cdsCiudad.data; if datos.cdsCiudad.ApplyUpdates(0) > 0 then begin if ultima_accion = dsInsert then begin // si se estaba agregando y no se pudo grabar el registro, entonces cierra // el formulario pq no puedo volver a editarlo. CerrarFormulario:= not true; // limpia el clientdataset datos.cdsCiudad.EmptyDataSet; datos.cdsCiudad.Insert; rd:= TClientDataSet.Create(nil); rd.Data:= bkp; // actualiza los campos con los valores previamente ingresados que se // hizo una copia antes de hacer apply_updates if datos.cdsCiudad.State = dsinsert then begin for i := 1 to dsBase.DataSet.Fields.Count do begin dsbase.DataSet.Fields.FieldByNumber(i).Value:= rd.Fields.FieldByNumber(i).Value; end; end; FreeAndNil(rd); end else begin // si se esta modificando CerrarFormulario:= false; // impide que el form sea cerrado Datos.cdsCiudad.Edit; end; abort; end; end; ]
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
procedure TServerMethods1.prvCiudadBeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean); var newcode: Integer; begin if UpdateKind = ukInsert then begin newcode:= NuevoID(1); DeltaDS.FieldByName('ID_CIUDAD').NewValue:= newcode; qrCiudad.Tag:= newcode; end; end;
[glow=red,2,300]en el evento AfterUpdateRecord del PROVIDER[/glow]
delphi
procedure TServerMethods1.prvCiudadAfterUpdateRecord(Sender: TObject; SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind); begin if (SourceDS = qrCiudad) and (UpdateKind <> ukDelete) then if Modificado(DeltaDS, 'NOM_CIUDAD') then AsociarNombres(DeltaDS.FieldByName('ID_CIUDAD').OldValue,'CIUDADES'); end;
[glow=red,2,300]en el evento BeforeApplyUpdate del PROVIDER[/glow]
delphi
procedure TServerMethods1.prvCiudadBeforeApplyUpdates(Sender: TObject; var OwnerData: OleVariant); begin qrCiudad.Tag:= -1; end;
[glow=red,2,300]en el evento AfterApplyUpdates del PROVIDER[/glow]
delphi
procedure TServerMethods1.prvCiudadAfterApplyUpdates(Sender: TObject; var OwnerData: OleVariant); var RecsOut: Integer; Params, OwnerData1: OleVariant; mycommand: string; begin mycommand:= 'SELECT C.*,D.NOM_DEPARTAMENTO,D.IDLEVER_DPTO from CIUDADES C '; mycommand:= mycommand+ 'INNER JOIN DEPARTAMENTOS_ATENDIDOS D '; mycommand:= mycommand+ ' ON d.ID_DEPARTAMENTO = c.ID_DEPARTAMENTO '; if qrCiudad.Tag > 0 then OwnerData := prvCiudades.GetRecords(-1, RecsOut, 0, Format(mycommand+' where ID_CIUDAD = %d', [qrCiudad.Tag]), Params, OwnerData1); 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
procedure TF_Ciudades.BnOkClick(Sender: TObject); var bkp: OleVariant; i: integer; rd: TCustomClientDataSet; DBXTrans: TDBXTransaction begin inherited; // respetar esta llamada; if datos.cdsCiudadNOM_CIUDAD.IsNull then begin MessageDlg('No puede dejar en blanco el nombre de la Ciudad !!!',mtwarning,[mbok],0); DBEdit1.SetFocus; CerrarFormulario:= false; exit; end; if datos.cdsCiudadID_DEPARTAMENTO.IsNull then begin MessageDlg('No puede dejar de seleccionar el Departamento donde se localiza!!!',mtwarning,[mbok],0); Btn_SelDepto.SetFocus; CerrarFormulario:= false; exit; end; // linea Agregada para iniciar supuestamente la transaccion aDBXTrans := SAMSYS.BeginTransaction(TDBXIsolations.ReadCommitted); Datos.cdsCiudad.CheckBrowseMode; bkp:= Datos.cdsCiudad.data; if datos.cdsCiudad.ApplyUpdates(0) > 0 then begin // RollBack transaction aDBXConn.RollbackFreeAndNil(aDBXTrans); if ultima_accion = dsInsert then begin // si se estaba agregando y no se pudo grabar el registro, entonces cierra // el formulario pq no puedo volver a editarlo. CerrarFormulario:= not true; // limpia el clientdataset datos.cdsCiudad.EmptyDataSet; datos.cdsCiudad.Insert; rd:= TClientDataSet.Create(nil); rd.Data:= bkp; // actualiza los campos con los valores previamente ingresados que se // hizo una copia antes de hacer apply_updates if datos.cdsCiudad.State = dsinsert then begin for i := 1 to dsBase.DataSet.Fields.Count do begin dsbase.DataSet.Fields.FieldByNumber(i).Value:= rd.Fields.FieldByNumber(i).Value; end; end; FreeAndNil(rd); end else begin // si se esta modificando CerrarFormulario:= false; // impide que el form sea cerrado Datos.cdsCiudad.Edit; end; abort; end; else // Commit transaction aDBXConn.CommitFreeAndNil(aDBXTrans); 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
var aConnName: string; aDBXConn: TDBXConnection; aDBXTrans: TDBXTransaction; aCmnd: TDBXCommand; aReader: TDBXReader; aColCount: integer; begin aDBXConn := TDBXConnectionFactory.GetConnectionFactory.GetConnection ('EMPLOYEE IB', 'sysdba', 'masterkey'); if aDBXConn <> nil then begin // Write the all connection parameters Writeln('================= Connection Properties ============'); Writeln(aDBXConn.ConnectionProperties.Properties.Text); Writeln('===================================================='); Writeln(''); aCmnd := aDBXConn.CreateCommand; // Start transaction aDBXTrans := aDBXConn.BeginTransaction(TDBXIsolations.ReadCommitted); // Prepare and execute the SQL Statement aCmnd.Text := 'SELECT * FROM Country'; aCmnd.Prepare; aReader := aCmnd.ExecuteQuery; aColCount := aReader.ColumnCount; Writeln('Results from Query: ' + aCmnd.Text); Writeln('Number of Columns: ' + IntToStr(aColCount)); while aReader.Next do begin Writeln(aReader.Value['Country'].GetAnsiString); end; Writeln('===================================================='); Writeln(''); // Commit transaction aDBXConn.CommitFreeAndNil(aDBXTrans); Readln; aReader.Free; aCmnd.Free; aDBXConn.Free; end; 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 pudomostrar 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

Transacciones Explicitas con Datasnap en multiples capas usando Providers
Comenzado por
nwac77
, may 18 2012 03:44
6 respuestas en este tema
#1
Escrito 18 mayo 2012 - 03:44
#2
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
#3
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.
Saludos
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
procedure TDatos.ValidarRequeridos(D: TDataSet); var h, K: Integer; begin for K := 0 to D.FieldCount - 1 do if D.Fields[K].Required then if (D.Fields[K].Text = '') or D.Fields[K].IsNull then begin D.Fields[K].FocusControl; raise Exception.Create('El campo ' + D.Fields[K].DisplayLabel + ' debe contener un valor. Recuerde que los campos marcados con ' + 'un asterísco son obligatorios'); end; end;
Saludos
#4
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:
Saludos
delphi
uses SysUtils, Classes, Provider, DB, SqlExpr; type TDataModule1 = class(TDataModule) SQLConnection1: TSQLConnection; DataSetProvider1: TDataSetProvider; procedure DataSetProvider1BeforeApplyUpdates(Sender: TObject; var OwnerData: OleVariant); procedure DataSetProvider1AfterApplyUpdates(Sender: TObject; var OwnerData: OleVariant); procedure DataSetProvider1UpdateError(Sender: TObject; DataSet: TCustomClientDataSet; E: EUpdateError; UpdateKind: TUpdateKind; var Response: TResolverResponse); private private T: TDbXTransaction; public { Public declarations } end; var DataModule1: TDataModule1; implementation {$R *.dfm} procedure TDataModule1.DataSetProvider1BeforeApplyUpdates(Sender: TObject; var OwnerData: OleVariant); begin T := SQLConnection1.BeginTransaction(TDBXIsolations.ReadCommitted); end; procedure TDataModule1.DataSetProvider1AfterApplyUpdates(Sender: TObject; var OwnerData: OleVariant); begin if SQLConnection1.InTransaction then SQLConnection1.CommitFreeAndNil(T); end; procedure TDataModule1.DataSetProvider1UpdateError(Sender: TObject; DataSet: TCustomClientDataSet; E: EUpdateError; UpdateKind: TUpdateKind; var Response: TResolverResponse); begin if SQLConnection1.InTransaction then SQLConnection1.RollbackFreeAndNil(T); end; end.
Saludos
#5
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).
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).
[move]Desde ya muchas gracias por tu atención.[/move]
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).

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]
#6
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
Saludos
#7
Escrito 19 mayo 2012 - 09:22
gracias