Ver Mensaje Individual
  #11  
Antiguo 05-06-2014
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Reputación: 29
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 Ricardo, encontré algo que puede servir, pero antes quisiera resolver una duda que me surge de lo que has comentado:
Cita:
Empezado por rcarrillom Ver Mensaje
El ChangeCount es para saber si se ha modificado el conjunto de datos en cualquier momento y para llevar un registro de cambios para un Deshacer. Si hay otra manera de saber simplemente si el conjunto ha cambiando, es bienvenida.
Eso me da a entender que no interesa saber la cantidad (el "count") de cambios ocurridos, sino solamente si han ocurrido o no. De ser así, ¿por qué no usas una simple bandera Boolean que pongas en True cuando inicies las operaciones de altas / bajas / cambios, y que pongas en False al hacer cualquier apply/merge?

Por otra parte, si quieres manejar "puntos de restauración" (marcar lo ya hecho para luego regresar), seguramente estás usando también la propiedad SavePoint, eso me parece bien.

En seguida te comento lo que encontré sobre el funcionamiento de la propiedad ChangeCount.

Cita:
Empezado por rcarrillom Ver Mensaje
[...] para depurar bien, activé la opcion Use debug dcus y no me lleva muy lejos, al llegar a la llamada cdsLog.ChangeCount me lleva a la unit Datasnap.DBClient(2669):
Código Delphi [-]
function TCustomClientDataSet.GetChangeCount: Int64;
begin
  Result := 0;
  if Active then
    Check(FDSBase.GetProp(dspropNOOFCHANGES, @Result));
end;

y ésta llamada me dirije a la unit Datasnap.DSIntf(662);
Código Delphi [-]
    function GetProp(               { Get property }
        eProp       : DSProp;
        piPropValue : Pointer
    ): DBResult; stdcall;

De ahi ya me pide el archivo ds.cpp que no está en mi sistema y ahi ya sale la ventana de CPU. Por el momento he llegado hasta aqui con el código fuente. Espero que alguien tenga más código fuente que yo, sé que las distintas versiones de Delphi vienen o no con fuentes dependiendo de lo caro que costó
Yo tengo los fuentes de MIDAS.dll por cortesía de un viejo y gran amigo del club. Gracias a estos archivos he podido hacer algunas mejorillas al TClientDataSet y ayudar a varios colegas con problemas similares al tuyo. Incluso me han servido para explicar a Embarcadero y a la Comunidad la causa de algunas pequeñas fallas. Claro, nunca faltará el zopenco que aun viendo las bondades de aquella acción (no solicitada, por cierto) de mi amigo, venga por aquí a decir que soy un pirata. Admitir que de Embarcadero quisiera comprar más de lo que he comprado, nunca es excusa suficiente para algunos.

En efecto Ricardo, el depurador me lleva también a ese punto del código, y a partir de ahí me veo en la necesidad de examinar el código de MIDAS manualmente. Esta es la parte que hace el cálculo de ChangeCount:
Código:
   case dspropNOOFCHANGES:
      if (piPropValue)
      {
         UINT32 i, iChanges = 0;

         if (pDsLog && pDsLog->iLast)
         {
            DSLOG *pLogNew = new DSLOG(pDsLog->iLast, NULL);

            if (pDsLog->CompactLog(pLogNew) == DBIERR_NONE && pLogNew->iLast > 0)
            {
               iChanges = pLogNew->iLast;
               for (i = 0; i < pLogNew->iLast; i++)
               {
                  LOGENTRY *pLogEntry = &pLogNew->pLogEntries[i];
                  if (pLogEntry->iAttr == dsRecModified)
                  {
                     if (RecsEqual(pLogEntry->iRecNo2, pLogEntry->iRecNo1, TRUE, NULL))
                        iChanges--; // Changes were canceled out
                  }
               }
....
Como podrás ver, hace un "pequeño" for. Pero lo realmente pesado está antes, al llamar al método CompactLog de pDsLog:
Código:
// Compact log, in order to create delta
DBIResult DSLOG::CompactLog(DSLOG *pLogNew)
{
   DBIResult rslt = DBIERR_NONE;
   pBYTE pValid = (pBYTE)DsCalloc(1, iLast +1);
   UINT32 iRecNo, iRecNoOrg;
   UINT32 i, j;

   if (pLogNew == NULL || pValid == NULL)
   {
      rslt = DBIERR_NOMEMORY;
      goto Exit;
   }

   for (i = 0; i < iLast; i++)
   {
...
El cual tiene cerca de 200 líneas de código con muchas validaciones sobre el estado de cada entrada del log. De ahí que demore demasiado cuando ya se han agregado decenas de miles de registros al conjunto de datos.

He de decir que encontré una manera de sacarle la vuelta al problema de la lentitud, pero no me gusta casi nada esta "solución", por dos razones: 1.- Porque no necesariamente se obtiene el mismo valor de la propiedad ChangeCount. Ésta hace un trabajo importante al compactar el registro de cambios, descartando ciertas entradas (como cuando agregas un registro y posteriormente lo eliminas). Lo que obtiene mi solución es la cantidad total de "entradas" en el registro de cambios: el valor de ese contador interno llamado iLast. 2.- Porque no es elegante, ya que implica "jaquear" (¿está bien escrito? ) la estructura de un par de clases contenidas en MIDAS.dll. Esto quiere decir que se deberá probar con mucho cuidado al cambiar de una versión de MIDAS a otra (por lo menos en Delphi 7 y XE2 me funcionó sin problemas).

De todas formas pongo aquí un ejemplo, por si te sirve a ti o a alguien más:
Código Delphi [-]
Type
  TClientDataSetAccess = Class (TClientDataSet);
procedure TForm1.Button1Click(Sender: TObject);
Type
  { Estructura para acceder al campo iLast de la clase interna DSLOG de
    MIDAS (alc_ds.h) }
  TDSLog = Packed Record
    Unused :Array [0..7] Of Byte;  // Primeros campos de la clase

    { NOTA: EntryCount no es necesariamente igual a la propiedad
      ChangeCount; puede ser mayor. }
    EntryCount :Cardinal;  // iLast
  End;

  { Estructura para acceder al campo pDsLog de la clase interna DSBASE de
    MIDAS (alchemy.h) }
  TDSBase = Packed Record
    Unused :Array [0..99] Of Byte;  // Primeros campos de la clase
    DSLog :^TDSLog;  // pDsLog
  End;
Var
  Base :^TDSBase;
  I :Integer;
begin
  // dt1 es un objeto TClientDataSet con dos campos (ID y Name)
  dt1.Close;
  dt1.CreateDataSet;

  For I := 1 To 50000 Do
    dt1.AppendRecord ([I, 'Test']);

  Base := Pointer (TClientDataSetAccess (dt1).DSBase);

  // Ambos mensajes muestran "50000"
  ShowMessage (IntToStr (Base.DSLog.EntryCount));  // Inmediato
  ShowMessage (IntToStr (dt1.ChangeCount));  // Demorado
end;
Ojalá hubiera mayor accesibilidad al interior de MIDAS, como para no tener que hacer este tipo de cosas. Pero como te dije al principio, si sólo necesitas saber si hubo o no hubo cambios, pienso que una simple bandera en tu código Delphi bastaría

Dos cosas más:

Cada vez que haces un Insert/Append/AppendRecord o un Post tradicional, se realizan diversas llamadas a métodos de TDataSet y TClientDataSet. Debes saber que puedes agregar de forma directa los valores a la memoria del ClientDataSet mediante su interfaz DSCursor (es una propiedad protegida, pero perfectamente accesible por derivación de clases), si bien el ahorro de tiempo no va a ser espectacular.

El otro asunto que comentabas, sobre el vaciado de la información a la base de datos, quizá sea más rápido si empleas sentencias SQL directas Insert Into usando tu componente conexión. Aunque también depende de qué componente sea éste y los motores y controladores que estén entre la aplicación y la base de datos.

No más rollo, se terminó la hora de trabajar en lo que me gusta y llegó la hora de trabajar en lo que me da para comer.

Un saludo.

Al González.
Responder Con Cita