Club Delphi  
    FTP   CCD     Buscar   Trucos   Trabajo   Foros

Retroceder   Foros Club Delphi > Principal > Conexión con bases de datos
Registrarse FAQ Miembros Calendario Guía de estilo Temas de Hoy

Conexión con bases de datos

Respuesta
 
Herramientas Buscar en Tema Desplegado
  #1  
Antiguo 10-03-2009
Bauhaus1975 Bauhaus1975 is offline
Miembro
 
Registrado: may 2005
Ubicación: Málaga
Posts: 135
Poder: 20
Bauhaus1975 Va por buen camino
Unhappy 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.
Responder Con Cita
  #2  
Antiguo 10-03-2009
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Poder: 30
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
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.

Última edición por Al González fecha: 11-03-2009 a las 17:25:09. Razón: Cambiar "Para eliminar..." por "Para eliminar o actualizar..."
Responder Con Cita
  #3  
Antiguo 11-03-2009
Bauhaus1975 Bauhaus1975 is offline
Miembro
 
Registrado: may 2005
Ubicación: Málaga
Posts: 135
Poder: 20
Bauhaus1975 Va por buen camino
Buenas,
Al Gónzalez ¡cuánto honor! que seas quien trate de guiarme entre este mar de sombras.

Cita:
Empezado por Al González Ver Mensaje
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.
Responder Con Cita
  #4  
Antiguo 12-03-2009
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Poder: 30
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
Cita:
Empezado por Bauhaus1975 Ver Mensaje
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.
Responder Con Cita
  #5  
Antiguo 12-03-2009
Bauhaus1975 Bauhaus1975 is offline
Miembro
 
Registrado: may 2005
Ubicación: Málaga
Posts: 135
Poder: 20
Bauhaus1975 Va por buen camino
Buenas de nuevo.

Cita:
Empezado por Al González Ver Mensaje
¿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.
Responder Con Cita
  #6  
Antiguo 12-03-2009
Bauhaus1975 Bauhaus1975 is offline
Miembro
 
Registrado: may 2005
Ubicación: Málaga
Posts: 135
Poder: 20
Bauhaus1975 Va por buen camino
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...
Responder Con Cita
  #7  
Antiguo 12-03-2009
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Poder: 30
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
Cita:
Empezado por Bauhaus1975 Ver Mensaje
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.
Responder Con Cita
  #8  
Antiguo 13-03-2009
Bauhaus1975 Bauhaus1975 is offline
Miembro
 
Registrado: may 2005
Ubicación: Málaga
Posts: 135
Poder: 20
Bauhaus1975 Va por buen camino
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.
Responder Con Cita
  #9  
Antiguo 13-03-2009
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Poder: 30
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
¡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.

Última edición por Al González fecha: 13-03-2009 a las 18:21:09.
Responder Con Cita
  #10  
Antiguo 16-03-2009
Bauhaus1975 Bauhaus1975 is offline
Miembro
 
Registrado: may 2005
Ubicación: Málaga
Posts: 135
Poder: 20
Bauhaus1975 Va por buen camino
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.
Responder Con Cita
  #11  
Antiguo 16-03-2009
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Poder: 30
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
Cita:
Empezado por Bauhaus1975 Ver Mensaje
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 Ver Mensaje
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.
Responder Con Cita
  #12  
Antiguo 17-03-2009
Bauhaus1975 Bauhaus1975 is offline
Miembro
 
Registrado: may 2005
Ubicación: Málaga
Posts: 135
Poder: 20
Bauhaus1975 Va por buen camino
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.
Responder Con Cita
Respuesta



Normas de Publicación
no Puedes crear nuevos temas
no Puedes responder a temas
no Puedes adjuntar archivos
no Puedes editar tus mensajes

El código vB está habilitado
Las caritas están habilitado
Código [IMG] está habilitado
Código HTML está deshabilitado
Saltar a Foro

Temas Similares
Tema Autor Foro Respuestas Último mensaje
applyupdates (record not found or changed by another user) Stell Conexión con bases de datos 4 13-05-2008 13:36:20
Error: "Record not found or changed by another user" jmlifi Varios 0 27-01-2006 10:16:57
Record not found or changed by another user felixgo Conexión con bases de datos 1 30-09-2005 13:07:40
Record not found or changed by another user. Luis Conexión con bases de datos 2 12-08-2005 19:50:45
error couldn't perform the edit because another user changed the record marcelofabiani Conexión con bases de datos 3 25-01-2005 01:55:11


La franja horaria es GMT +2. Ahora son las 11:46:21.


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