Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Conexión con bases de datos (https://www.clubdelphi.com/foros/forumdisplay.php?f=2)
-   -   TClientDataSet.applyupdates (error: record not found or changed by another user) (https://www.clubdelphi.com/foros/showthread.php?t=63945)

Bauhaus1975 10-03-2009 12:44:36

TClientDataSet.applyupdates (error: record not found or changed by another user)
 
Hola a todos, de nuevo aquí me encuentro con un error que me está volviendo loco, llevo varios días con esto.

Se trata del error que da el programa ha ejecutar la sentencia:
clientdataset.applyupdates
error: record not found or changed by another user.

(Trabajo con Firebird 2.1.1 y Borland Studio 2005) Uso los componentes de Interbase para conexión a BBDD, siempre sin problemas.

Las sentencias son las habituales, como puede verse:
Código Delphi [-]
// cds es el ClientDataSet
// 1. Ponemos en modo de edición
cds.Refresh(); // aunque es prescindible
cds.Edit();
// 2. Modificamos algunos campos
cds.FieldByName('Campo1').AsString := 'valor1';
// 3. Actualiza el registro en curso
cds.Post();
// 4. Hacemos efectivos los cambios
if cds.ApplyUpdates(0) <> 0 then
    raise Exception.Create('error grabando');
self.transaction.Commit;

Esto es lo habitual, aunque he aislado el código, en el programa todo es más complejo, no sé... he probado a ejecutar varias máquinas, no hay problema de permisos etc. Con el IB_Expert puedo modificar bien.
Ni idea...

¿Alguien tiene alguna idea?
Muchas gracias por vuestra ayuda.

Al González 10-03-2009 17:47:49

Para eliminar o actualizar el registro, el componente proveedor necesita identificarlo de algún modo. Normalmente es mediante el campo que tenga la bandera pfInKey en ProviderFlags. Pero eso depende de qué valor tenga la propiedad UpdateMode del proveedor. Por lo general se recomienda upWhereKeyOnly.

En DBConsts.pas se encuentra la constante:
SRecordChanged = 'Record not found or changed by another user';
la cual es usada en varios puntos de la unidad Provider.pas

Si compilas el programa con la opción Use Debug DCUs (del cuadro de diálogo Project Options), a la hora de saltar una excepción el IDE te proporcionará más información sobre qué camino siguió el programa hasta llegar a la condición de error. Usando la pila de llamadas (Ctrl+Alt+S) podrás ver, en este caso, en qué punto de la unidad Provider no se cumplió alguna condición necesaria para la correcta aplicación de cambios.

Te sugiero revisar las propiedades ProviderFlags y UpdateMode.

Saludos.

Al González. :)

Bauhaus1975 11-03-2009 10:59:12

Buenas,
Al Gónzalez ¡cuánto honor! que seas quien trate de guiarme entre este mar de sombras.

Cita:

Empezado por Al González (Mensaje 340847)
Para eliminar el registro, el componente proveedor necesita identificarlo de algún modo. Normalmente es mediante el campo que tenga la bandera pfInKey en ProviderFlags. Pero eso depende de qué valor tenga la propiedad UpdateMode del proveedor. Por lo general se recomienda upWhereKeyOnly.

No sé a que te refieres con eliminar. Solamente he obtenido un registro de la tabla 'clientes' y muestro sus datos en el formulario con controles 'data-aware'. El error me sale al editar. La consulta no cruza tablas, y la propiedad en el provider (TDataSetProvider) está UpdateMode=upWhereKeyOnly y creo que no debe dar problemas, ¿cierto?

Bien, ahora paso a intentar dar más información porque no soy capaz de aclararme viendo el debug de todo lo que me has recomendado.
Esto es lo que veo:

En la unit DBClient ejecutando el método 'TCustomClientDataSet.GetDelta', en la última línea 'DataPacketToVariant(TempPacket, Result);' Tras ejecutarla y ponerse sobre el 'end', llama posteriormente a una serie de código ensamblador en la unit 'System', tras lo cual es dónde se muestra la excepción. Creo entender que el código ensamblador ese es lo que lanza la expceción, pero no puedo sacar nada en claro.

La líneas de ensamblador que comento. Unit 'System':
Código Delphi [-]
function _IntfClear(var Dest: IInterface): Pointer;
{$IFDEF PUREPASCAL}
var
  P: Pointer;
begin
  Result := @Dest;
  if Dest <> nil then
  begin
    P := Pointer(Dest);
    Pointer(Dest) := nil;
    IInterface(P)._Release;
  end;
end;
{$ELSE}
asm
        MOV     EDX,[EAX]
        TEST    EDX,EDX
        JE      @@1
        MOV     DWORD PTR [EAX],0
        PUSH    EAX
        PUSH    EDX
        MOV     EAX,[EDX]
        CALL    DWORD PTR [EAX] + VMTOFFSET IInterface._Release
        POP     EAX
@@1:
end;

¿Podemos avanzar con esta nueva información?
Un saludo y gracias por la ayuda.

Al González 12-03-2009 09:16:40

Cita:

Empezado por Bauhaus1975 (Mensaje 340946)
No sé a que te refieres con eliminar

Quise decir eliminar o actualizar. En ambos casos es necesario que el proveedor identifique al registro en cuestión mediante uno o varios campos. Normalmente es por el campo de llave primaria cuando UpdateMode es upWhereKeyOnly, pero siempre y cuando dicho campo tenga la bandera pfInKey. ¿Ya revisaste la propiedad ProviderFlags de ese campo en ambos conjuntos de datos? :)

Respecto al depurador, cuando ocurra la excepción abre la ventana Call Stack (pila de llamadas) la cual te mostrará cómo fueron ejecutándose las rutinas una tras otra hasta llegar al punto de la excepción. El elemento superior de esa lista es la rutina más actual o reciente (donde ahora está detenido el programa, vaya), la cual fue llamada por la segunda de la lista, y ésta a su vez por la tercera, etc.

Dando doble clic sobre alguna de ellas, te mostrará el punto exacto del código fuente donde esa rutina llamó a la que le sigue arriba. En alguna de las primeras, es muy probable que encuentres una sentencia como:
Código Delphi [-]
DatabaseError(SRecordChanged);
.

Con eso también puede indagarse qué validación no se cumplió en TDataSetProvider o alguna otra clase asociada, para que la excepción SRecordChanged fuera lanzada.

Espero haber aclarado un poco el asunto, por favor indícanos tus dudas y avances.

Saludos.

Al. :)

Bauhaus1975 12-03-2009 10:15:24

Buenas de nuevo.

Cita:

Empezado por Al González (Mensaje 341136)
¿Ya revisaste la propiedad ProviderFlags de ese campo en ambos conjuntos de datos? :)

Creo que te refieres a ver la propiedad del TField 'ID' que es la clave de la tabla. Pero no puedo (o sé) ver la lista de campos. Cuando va a realizar el cds.Applyupdates, trato de ver los campos del clientdataset (bajo el objeto TDataSet), pero son listas que muestran una direccion de memoria.
TDataSet-> FFields -> FDataSet -> FFields etc. no hay manera de ver mi lista de campos.

Por otra parte he visto usando el depurador detenidamente que al ejecutar el siguiente método del provider:
Código Delphi [-]
procedure TSQLResolver.DoExecSQL(SQL: TWideStringList; Params: TParams);
var
  RowsAffected: Integer;
  PS2: IProviderSupport2;
begin
  if Supports(Provider.DataSet, IProviderSupport2, PS2) then
    RowsAffected := PS2.PSExecuteStatement(SQL.Text, Params)
  else
    RowsAffected := (Provider.DataSet as IProviderSupport).PSExecuteStatement(SQL.Text, Params);
  if not (poAllowMultiRecordUpdates in Provider.Options) and (RowsAffected > 1) then
  begin
    if Assigned(PS2) then
      PS2.PSEndTransaction(False)
    else
      (Provider.DataSet as IProviderSupport).PSEndTransaction(False);
    Provider.TransactionStarted := False;
    DatabaseError(STooManyRecordsModified);
  end;
  if RowsAffected < 1 then
    DatabaseError(SRecordChanged);
end;

Ejecuta la línea -> RowsAffected := PS2.PSExecuteStatement(SQL.Text, Params)

Y RowsAffected queda con valor '0' tras ello, y por tanto es lo que hace que se lance la excepción (en la última línea del procedimiento)

No sé, tendrá que ser una tonteria (ni idea).
Creo que sería más fácil aislar el codigo y dejártelo ver o ejecutar a ver que opinas.

Gracias de nuevo.

Bauhaus1975 12-03-2009 11:24:40

Bien, he puesto un bucle para recorrer los campos del ClientDataSet y mirar sus propiedades.
De esta manera he visto que el campo 'ID' tiene como ProviderFlags: [pfInUpdate..pfInKey]
Y el resto de campos (estando todos ok con sus valores obtenidos):
[pfInUpdate..pfInWhere]

No sé esto podrá ayudar...

Al González 12-03-2009 18:14:03

Cita:

Empezado por Bauhaus1975 (Mensaje 341143)
Por otra parte he visto usando el depurador detenidamente que al ejecutar el siguiente método del provider:
Código Delphi [-]
procedure TSQLResolver.DoExecSQL(SQL: TWideStringList; Params: TParams);
var
  RowsAffected: Integer;
  PS2: IProviderSupport2;
begin
  if Supports(Provider.DataSet, IProviderSupport2, PS2) then
    RowsAffected := PS2.PSExecuteStatement(SQL.Text, Params)
  else
    RowsAffected := (Provider.DataSet as IProviderSupport).PSExecuteStatement(SQL.Text, Params);
  if not (poAllowMultiRecordUpdates in Provider.Options) and (RowsAffected > 1) then
  begin
    if Assigned(PS2) then
      PS2.PSEndTransaction(False)
    else
      (Provider.DataSet as IProviderSupport).PSEndTransaction(False);
    Provider.TransactionStarted := False;
    DatabaseError(STooManyRecordsModified);
  end;
  if RowsAffected < 1 then
    DatabaseError(SRecordChanged);
end;

Ejecuta la línea -> RowsAffected := PS2.PSExecuteStatement(SQL.Text, Params)

Y RowsAffected queda con valor '0' tras ello, y por tanto es lo que hace que se lance la excepción (en la última línea del procedimiento).

Eso me temía. Ahora, ¿podrías detener el programa en esa línea que señalas y decirnos qué valor tienen la propiedad SQL.Text y los parámetros de la colección Params (Params.Count, Params [0].Value, Params [1].Value...)?

En cuanto a las banderas del campo ID, parecen estar bien. La clave va a estar en esa sentencia SQL Update (SQL.Text) que no afecta a la base de datos, probablemente porque los parámetros indican un registro inexistente.

Esperamos retroalimentación.

Al González. :)

Bauhaus1975 13-03-2009 12:52:33

Buenas de nuevo, allá vamos. Primero respondo, y luego agrego

Con el Debug detenido justo antes de la sentencia que lanza la excepción 'DatabaseError(SRecordChanged);'. Ello en el método y unit: procedure TSQLResolver.DoExecSQL(SQL: TWideStringList; Params: TParams);

He mirado el contenido de SQL.Text, sólo pidiendolo sobre el código porque sobre el inspector de objetos 'local variables', el depurador no mostraba tal propiedad. Este es su contenido, tras haber añadido informacion a cada campo directamente, para acotar el error más a fondo:

Código SQL [-]

SQL.Text 'update "CLIENTE"  set'#$D#$A' "FORMAPAGO" = ?,'#$D#$A' "IDUSUARIO" = ?,'#$D#$A' 
"NOMBRE" = ?,'#$D#$A' "DOMICILIO" = ?,'#$D#$A' "TELEFONO1" = ?,'#$D#$A' "TELEFONO2" = 
?,'#$D#$A' "WEB" = ?,'#$D#$A' "FAX" = ?,'#$D#$A' "FECHAALTA" = ?,'#$D#$A' "IDCONTACTO" = 
?,'#$D#$A' "OBSERVACIONES" = ?'#$D#$A'where'#$D#$A' "ID" = ? and'#$D#$A' "NIF" = ? 
and'#$D#$A' "FORMAPAGO" is null and'#$D#$A' "IDUSUARIO" = ? and'#$D#$A' "PUBLICO" = ? 
and'#$D#$A' "ACTIVIDAD" = ? and'#$D#$A' "NOMBRE" = ? and'#$D#$A' "APELLIDOS" = ? 
and'#$D#$A' "DOMICILIO" = ? and'#$D#$A' "CP" = ? and'#$D#$A' "CODIGOPROVINCIA" = ? 
and'#$D#$A' "MUNICIPIO" = ? and'#$D#$A' "EMAIL" = ? and'#$D#$A' "TELEFONO1" = ? 
and'#$D#$A' "TELEFONO2" is null and'#$D#$A' "WEB" is null and'#$D#$A' "FAX" is null 
and'#$D#$A' "FECHAALTA" = ? and'#$D#$A' "IDCONTACTO" is null and'#$D#$A' "OBSERVACIONES" 
is null'#$D#$A

Y Esta es la query realizada, que inicialmente obtiene los datos del cliente para mostrarlos en el formulario:

Código SQL [-]
'SELECT c.ID as 
IDCliente,c.NIF,c.FormaPago,c.IDUsuario,c.Publico,c.Actividad,c.Nombre,c.Apellidos,c.Domic
ilio,c.CP,c.CodigoProvincia,c.Municipio,c.EMail,c.Telefono1,c.Telefono2,c.Web,c.Fax,c.Fech
aAlta,c.IDContacto,c.Observaciones FROM cliente c WHERE ID = 1'

Ahora viene lo mejor, ayer estuve haciendo un programa de prueba para aislar la funcionalidad que puede ocasionar el problema. Cree una tabla cliente con tres campos (ID,Nombre,Direccion) añadi las clases implicadas, y... ¡voila! funcionaba perfectamente. ¿Qué puedo hacer? pues seguramente alguna parte del programa está afectando, pero mira que llevo horas depurando, no hay queries que 'intoxiquen' al provider, ni nada que yo haya visto que pueda afectar. Lo único que se me ocurre es ir añadiendo código a mi programa de prueba hasta que ocurra el mismo error que ahora se da en el programa completo.

¿Alguna idea más? Gracias y un saludo.

Al González 13-03-2009 18:15:33

¡Hola!

Excelente dato. No sé si ya notaste que esa sentencia "Update Cliente..." no está usando upWhereKeyOnly, sino que pareciera usar upWhereAll.

Cita:

Empezado por ayuda de Delphi
TUpdateMode is the type of the UpdateMode property. UpdateMode specifies how the records are located when the ApplyUpdates method is called. TUpdateMode includes the following values:

Value Meaning

upWhereAll All columns (fields) are used to locate the record.
upWhereChanged Only key field values and the original value of fields that have changed are used to find the record.
upWhereKeyOnly Only key fields are used to find the record.

Está usando un "Where" con todos los campos para intentar localizar el registro. Esto, además de ser poco eficiente, suele presentar el problema que nos reportas desde el comienzo del hilo (normalmente es porque otro usuario o programa modifica el registro, pero también puede pasar que alguno de los parámetros para el Where no haya sido obtenido correctamente).

En teoría, si UpdateMode fuese upWhereKeyOnly, ese "Update Cliente..." debería terminar en un simple "Where ID = ?", y no incluir a todos los demás campos en la cláusula Where. Funcionando entonces correctamente.

Nuevamente, al estar detenido el programa en ese punto, revisa qué valor tiene la propiedad Provider.UpdateMode. No con el inspector, sino metiendo "Provider.UpdateMode" a las observaciones (watches). Todo parece indicar que NO es upWhereKeyOnly, sino upWhereAll. ¿Alguna parte del programa estará cambiando esa propiedad? :)

Esperamos tus avances.

Al González. :)

Bauhaus1975 16-03-2009 11:54:53

Hola de nuevo,
lamento la tardanza en responder, resulta que he estado estudiando bien en el tema. Y tienes razón, se colaba en un sitio no esperado el cambio de la propiedad Provider.UpdateMode a upWhereAll. Cuanto siento el tiempo que hemos perdido por algo que no era tan complicado en realidad.

Después de 'arreglar' este error he seguido con problemas asociados pues el provider era usado por otras queries (en algunos casos del programa) y perdía la referencia del registro del DataSet original. Quizá por simplificar (usar un Provider que se alimenta de varias queries) he terminado complicando más el tema.

Bueno, muchas gracias de nuevo por tu valiosa ayuda.
Volveremos a vernos pronto, seguro.
Saludos.

Al González 16-03-2009 19:47:49

Cita:

Empezado por Bauhaus1975 (Mensaje 341554)
Cuanto siento el tiempo que hemos perdido por algo que no era tan complicado en realidad.

Descuida. No es para mí una pérdida de tiempo, y creo que para ti tampoco. Este ejercicio ha sido útil para ambos y algo de lo dicho podrá servirle también a otros. :)

Cita:

Empezado por Bauhaus1975 (Mensaje 341554)
Quizá por simplificar (usar un Provider que se alimenta de varias queries) he terminado complicando más el tema.

Puede que valga la pena abrir un hilo sobre esa cuestión particular.

Bauhaus1975 17-03-2009 11:30:51

Bien, entonces quizá lo mejor es que cuando tenga listo y depurado el tema haga una exposición de cómo he organizado la distribución de Queries+Providers+DataSet y discutamos ¿cierto?.

Esta organización la estoy usando en una clase genérica que he creado, la cual define un comportamiento bastante común: sacar listados de esa entidad, obtener datos de ficha, actualizar, etc. Y clases que siempre suelen funcionar igual como 'clientes', 'contactos', 'proveedores' etc la heredan.

Gracias y un saludo.


La franja horaria es GMT +2. Ahora son las 12:31:45.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi