Pregunta

Observé algo la semana pasada que no esperaba y lo describiré a continuación.Tengo curiosidad por saber por qué sucede esto.¿Es algo interno de la clase TDataSet, un artefacto de TDBGrid o algo más?

El orden de los campos en un ClientDataSet abierto cambió.Específicamente, creé un ClientDataSet en código llamando a CreateDatatSet después de definir su estructura usando FieldDefs.El primer campo en la estructura de este ClientDataSet era un campo de fecha llamado StartOfWeek.Momentos después, el código que también había escrito, que suponía que el campo StartOfWeek estaba en la posición cero, ClientDataSet.Fields[0], falló, ya que el campo StartOfWeek ya no era el primer campo en ClientDataSet.

Después de investigar un poco, descubrí que era posible que cada campo del ClientDataSet pudiera, en un momento dado, aparecer en alguna posición diferente de la estructura original en el momento en que se creó el ClientDataSet.No sabía que esto podía suceder y una búsqueda en Google tampoco arrojó ninguna mención de este efecto.

Lo que pasó no fue mágico.Los campos no cambiaron de posición por sí solos ni cambiaron en función de nada que hice en mi código.Lo que causó que los campos pareciera que físicamente cambiaban de posición en ClientDataSet fue que el usuario había cambiado el orden de las columnas en un DbGrid al que se adjuntó ClientDataSet (a través de un componente DataSource, por supuesto).Repliqué este efecto en Delphi 7, Delphi 2007 y Delphi 2010.

Creé una aplicación Delphi muy simple que demuestra este efecto.Consta de un formulario único con un DBGrid, un DataSource, dos ClientDataSets y dos Buttons.El controlador de eventos OnCreate de este formulario se parece al siguiente

procedure TForm1.FormCreate(Sender: TObject);
begin
  with ClientDataSet1.FieldDefs do
  begin
    Clear;
    Add('StartOfWeek', ftDate);
    Add('Label', ftString, 30);
    Add('Count', ftInteger);
    Add('Active', ftBoolean);
  end;
  ClientDataSet1.CreateDataSet;
end;

El botón1, que tiene la etiqueta Mostrar estructura ClientDataSet, contiene el siguiente controlador de eventos OnClick.

procedure TForm1.Button1Click(Sender: TObject);
var
  sl: TStringList;
  i: Integer;
begin
  sl := TStringList.Create;
  try
    sl.Add('The Structure of ' + ClientDataSet1.Name);
    sl.Add('- - - - - - - - - - - - - - - - - ');
    for i := 0 to ClientDataSet1.FieldCount - 1 do
      sl.Add(ClientDataSet1.Fields[i].FieldName);
    ShowMessage(sl.Text);
  finally
    sl.Free;
  end;
end;

Para demostrar el efecto de campo en movimiento, ejecute esta aplicación y haga clic en el botón denominado Mostrar estructura de ClientDataSet.Deberías ver algo como lo que se muestra aquí:

The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - - 
StartOfWeek
Label
Count
Active

A continuación, arrastre las columnas de DBGrid para reorganizar el orden de visualización de los campos.Haga clic en el botón Mostrar estructura de ClientDataSet una vez más.Esta vez verás algo similar a lo que se muestra aquí:

The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - - 
Label
StartOfWeek
Active
Count

Lo notable de este ejemplo es que las columnas de DBGrid se están moviendo, pero hay un efecto aparente en la posición de los campos en ClientDataSet, de modo que el campo que estaba en la posición ClientDataSet.Field[0] en una El punto no está necesariamente ahí momentos después.Y, desafortunadamente, esto no es claramente un problema de ClientDataSet.Realicé la misma prueba con TTables basadas en BDE y AdoTables basadas en ADO y obtuve el mismo efecto.

Si nunca necesita hacer referencia a los campos de su ClientDataSet que se muestran en un DBGrid, entonces no tiene que preocuparse por este efecto.Para el resto de ustedes, se me ocurren varias soluciones.

La forma más sencilla, aunque no necesaria, de evitar este problema es evitar que el usuario reordene los campos en un DBGrid.Esto se puede hacer eliminando el indicador dgResizeColumn de la propiedad Opciones de DBGrid.Si bien este enfoque es eficaz, elimina una opción de visualización potencialmente valiosa, desde la perspectiva del usuario.Además, eliminar esta marca no solo restringe el reordenamiento de las columnas, sino que también evita el cambio de tamaño de las mismas.(Para aprender cómo limitar el reordenamiento de las columnas sin eliminar la opción de cambio de tamaño de las columnas, consulte http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm.)

La segunda solución es evitar hacer referencia a los campos de un DataSet en función de su posición literal (ya que esta es la esencia del problema).En otras palabras, si necesita consultar el campo Recuento, no utilice DataSet.Fields[2].Siempre que conozca el nombre del campo, puede usar algo como DataSet.FieldByName('Count').

Sin embargo, existe un gran inconveniente en el uso de FieldByName.Específicamente, este método identifica el campo iterando a través de la propiedad Campos del DataSet, buscando una coincidencia basada en el nombre del campo.Dado que hace esto cada vez que llama a FieldByName, este es un método que debe evitarse en situaciones en las que es necesario hacer referencia al campo muchas veces, como en un bucle que navega por un conjunto de datos grande.

Si necesita consultar el campo repetidamente (y una gran cantidad de veces), considere usar algo como el siguiente fragmento de código:

var
  CountField: TIntegerField;
  Sum: Integer;
begin
  Sum := 0;
  CountField := TIntegerField(ClientDataSet1.FieldByName('Count'));
  ClientDataSet1.DisableControls;  //assuming we're attached to a DBGrid
  try
    ClientDataSet1.First;
    while not ClientDataSet1.EOF do
    begin
      Sum := Sum + CountField.AsInteger;
      ClientDataSet1.Next;
    end;
  finally
    ClientDataSet1.EnableControls;
  end;

Existe una tercera solución, pero solo está disponible cuando su DataSet es un ClientDataSet, como el de mi ejemplo original.En esas situaciones, puede crear un clon del ClientDataSet original y tendrá la estructura original.Como resultado, cualquier campo que se haya creado en la posición cero seguirá estando en esa posición, independientemente de lo que un usuario haya hecho con un DBGrid que muestra los datos de ClientDataSets.

Esto se demuestra en el siguiente código, que está asociado con el controlador de eventos OnClick del botón denominado Mostrar estructura Cloned ClientDataSet.

procedure TForm1.Button2Click(Sender: TObject);
var
  sl: TStringList;
  i: Integer;
  CloneClientDataSet: TClientDataSet;
begin
  CloneClientDataSet := TClientDataSet.Create(nil);
  try
    CloneClientDataSet.CloneCursor(ClientDataSet1, True);
    sl := TStringList.Create;
    try
      sl.Add('The Structure of ' + CloneClientDataSet.Name);
      sl.Add('- - - - - - - - - - - - - - - - - ');
      for i := 0 to CloneClientDataSet.FieldCount - 1 do
        sl.Add(CloneClientDataSet.Fields[i].FieldName);
      ShowMessage(sl.Text);
    finally
      sl.Free;
    end;
  finally
    CloneClientDataSet.Free;
  end;
end;

Si ejecuta este proyecto y hace clic en el botón denominado Mostrar estructura Cloned ClientDataSet, siempre obtendrá la verdadera estructura de ClientDataSet, como se muestra aquí.

The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - - 
StartOfWeek
Label
Count
Active

Apéndice:

Es importante señalar que la estructura real de los datos subyacentes no se ve afectada.Específicamente, si, después de cambiar el orden de las columnas en un DBGrid, llama al método SaveToFile del ClientDataSet, la estructura guardada es la estructura original (verdadera interna).Además, si copia la propiedad Datos de un ClientDataSet a otro, el ClientDataSet de destino también muestra la estructura verdadera (que es similar al efecto observado cuando se clona un ClientDataSet de origen).

De manera similar, los cambios en el orden de las columnas de DBGrids vinculados a otros conjuntos de datos probados, incluidos TTable y AdoTable, en realidad no afectan la estructura de las tablas subyacentes.Por ejemplo, una TTable que muestra datos de la tabla Paradox de muestra customer.db que se envía con Delphi en realidad no cambia la estructura de esa tabla (ni esperaría que lo hiciera).

Lo que podemos concluir de estas observaciones es que la estructura interna del propio DataSet permanece intacta.Como resultado, debo asumir que hay una representación secundaria de la estructura del DataSet en alguna parte.Y debe estar asociado con el DataSet (lo que parecería excesivo, ya que no todos los usos de un DataSet lo necesitan), asociado con el DBGrid (lo cual tiene más sentido ya que el DBGrid usa esta característica, pero no respaldado por la observación de que la reordenación de TField parece persistir con el conjunto de datos en sí), o es algo más.

Otra alternativa es que el efecto esté asociado con TGridDataLink, que es la clase que proporciona a los controles multifila (como DBGrids) su reconocimiento de datos.Sin embargo, me inclino a rechazar esta explicación también, ya que esta clase está asociada con la cuadrícula, y no con el DataSet, nuevamente porque el efecto parece persistir con las clases mismas de DataSet.

Lo que me lleva de vuelta a la pregunta original.¿Es este efecto algo interno de la clase TDataSet, un artefacto de TDBGrid o algo más?

Permítame también enfatizar algo aquí que agregué a uno de los comentarios a continuación.Más que nada, mi publicación está diseñada para concienciar a los desarrolladores de que cuando usan DBGrids cuyo orden de columnas se puede cambiar, el orden de sus TFields también puede estar cambiando.Este artefacto puede introducir errores intermitentes y graves que pueden ser muy difíciles de identificar y solucionar.Y no, no creo que esto sea un error de Delphi.Sospecho que todo está funcionando como fue diseñado para funcionar.Es sólo que muchos de nosotros no sabíamos que este comportamiento estaba ocurriendo.Ahora sabemos.

¿Fue útil?

Solución

Aparentemente el comportamiento es por diseño.De hecho, no está relacionado con dbgrid.Es simplemente un efecto secundario de una columna que establece un índice de campo.Por ejemplo esta declaración,

ClientDataSet1.Fields[0].Index := 1;

hará que la salida del botón "Mostrar estructura ClientDataSet" cambie en consecuencia, ya sea que haya una cuadrícula o no.La documentación para TField.Index dice;

"Cambie el orden de la posición de un campo en el conjunto de datos cambiando el valor de Índice.Cambiar el valor del Índice afecta el orden en que se muestran los campos en las cuadrículas de datos, pero no la posición de los campos en las tablas de bases de datos físicas".

Se debería concluir que lo contrario también debería ser cierto y que cambiar el orden de los campos en una cuadrícula debería provocar que se cambien los índices de los campos.


El código que causa esto está en TColumn.SetIndex.TCustomDBGrid.ColumnMoved establece un nuevo índice para la columna movida y TColumn.SetIndex establece el nuevo índice para el campo de esa columna.

procedure TColumn.SetIndex(Value: Integer);
[...]
        if (Col <> nil) then
        begin
          Fld := Col.Field;
          if Assigned(Fld) then
            Field.Index := Fld.Index;
        end;
[...]

Otros consejos

Wodzu publicó una solución al problema de campo reordenado que era específica de ADO DataSet, pero me llevó a una solución similar y disponible para todos los DataSets (si se implementa correctamente en todo DataSets es otro problema).Tenga en cuenta que ni esta respuesta, ni la de Wodzu, son en realidad una respuesta a la pregunta original.Más bien, es una solución al problema señalado, mientras que la pregunta se refiere a dónde se origina este artefacto.

La solución a la que me llevó la solución de Wodzu fue FieldByNumber, y es un método de la propiedad Fields.Hay dos aspectos interesantes en el uso de FieldByNumber.Primero, debes calificar su referencia con la propiedad Fields de tu DataSet.En segundo lugar, a diferencia de la matriz Fields, que toma un indexador de base cero, FieldByNumber es un método que toma un parámetro de base uno para indicar la posición del TField al que desea hacer referencia.

La siguiente es una versión actualizada del controlador de eventos Button1 que publiqué en mi pregunta original.Esta versión utiliza FieldByNumber.

procedure TForm1.Button1Click(Sender: TObject);
var
  sl: TStringList;
  i: Integer;
begin
  sl := TStringList.Create;
  try
    sl.Add('The Structure of ' + ClientDataSet1.Name +
      ' using FieldByNumber');
    sl.Add('- - - - - - - - - - - - - - - - - ');
    for i := 0 to ClientDataSet1.FieldCount - 1 do
      sl.Add(ClientDataSet1.Fields.FieldByNumber(i + 1).FieldName);
    ShowMessage(sl.Text);
  finally
    sl.Free;
  end;
end;

Para el proyecto de muestra, este código produce el siguiente resultado, independientemente de la orientación de las columnas en el DBGrid asociado:

The Structure of ClientDataSet1 using FieldByNumber
- - - - - - - - - - - - - - - - - 
StartOfWeek
Label
Count
Active

Para repetir, observe que la referencia al TField subyacente requería que FieldByNumber estuviera calificado con una referencia a Fields.Además, el parámetro para este método debe estar dentro del rango de 1 a DataSet.FieldCount.Como resultado, para hacer referencia al primer campo del DataSet, utiliza el siguiente código:

ClientDataSet1.Fields.FieldByNumber(1)

Al igual que la matriz Fields, FieldByNumber devuelve una referencia TField.Como resultado, si desea hacer referencia a un método que es específico de una clase TField particular, debe convertir el valor devuelto a la clase apropiada.Por ejemplo, para guardar el contenido de TBlobField en un archivo, es posible que deba hacer algo como el siguiente código:

TBlobField(MyDataSet.Fields.FieldByNumber(6)).SaveToFile('c:\mypic.jpg');

Tenga en cuenta que no estoy sugiriendo que deba hacer referencia a TFields en un conjunto de datos utilizando literales enteros.Personalmente, el uso de una variable TField que se inicializa mediante una llamada única a FieldByName es más legible y es inmune a los cambios en el orden físico de la estructura de una tabla (¡aunque no inmune a los cambios en los nombres de sus campos!).

Sin embargo, si tiene conjuntos de datos asociados con DBGrids cuyas columnas se pueden reordenar y hace referencia a los campos de estos conjuntos de datos utilizando literales enteros como indexadores de la matriz de campos, es posible que desee considerar convertir su código para usar el método DataSet.Fields.FieldByName. .

Cary Creo que he encontrado una solución para este problema.En lugar de usar campos contenedores VCL, necesitamos usar una propiedad de campos interna del objeto COM Recordset.

Así es como se debe hacer referencia:

qry.Recordset.Fields.Item[0].Value

Esos campos NO se ven afectados por el comportamiento que describió anteriormente.Entonces todavía podemos referirnos a los campos por su índice.

Pruebe esto y dígame cuál fue el resultado.Funcionó para mí.

Editar:

Por supuesto, funcionará sólo para componentes ADO, no para TClientDataSet...

Editar2:

Cary, no sé si esta es la respuesta a tu pregunta, sin embargo, he estado presionando a la gente en los foros de Embarcadero y Wayne Niddery me dio una respuesta bastante detallada sobre todo este movimiento de Fields.

Para acortar una historia larga: Si define sus columnas en TDBGrid explícitamente, ¡los índices de campo no se mueven! Ahora tienes un poco más de sentido común, ¿no?

Lea el hilo completo aquí:https://forums.embarcadero.com/post!reply.jspa?messageID=197287

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top