Как редактировать набор данных? - PullRequest
0 голосов
/ 12 марта 2020

Короткая версия

Как мне позвонить:

dataset1.FieldByName(fieldName).AsString := 'Something';

и заставить это работать?

Длинная версия

У меня есть DataSet :

var
    ds: TDataSet;

    ds := GetSomeSortOfDataSetFromSomewhere();

Этот набор данных будет экспортирован (например, в Excel, cSV, TSV, Markdown, HTML, XML):

ExportDataSet(ds);

, и экспорт будет содержать все столбцы и все строки:

| Username | Fullname                |
|----------|-------------------------|
| ian      | IAN BOYD                |
| MartynA  | MARTIN                  |
| ngal     | NASREDDINE GALFOUT      |
| uewr     | UWE RAABE               |

Теперь я хочу изменить поле Fullname для каждой строки в памяти , прежде чем делать с ним что-то еще (т. е. никогда не возвращаться в база данных, я не знаю, откуда она взялась, возможно, она не пришла из базы данных):

while not ds.EOF do
begin
    ds.FieldByName('Fullname').AsString := FormatNamePrettyLike(ds.FieldByName('Fullname').AsString;
    ds.Next;
end;

При попытке изменить поле выдается исключение:

Набор данных не находится в режиме редактирования или вставки

Решение состоит в том, чтобы клонировать набор данных в оперативную память TClientDataset:

///<summary>Clones a dataset into a TClientDataSet; which is an editable in-memory DataSet.</summary>
function CloneDataSet(dsSource: TDataSet): TDataSet; //TDataSet > TCustomClientDataSet > TClientDataSet
var
    tempProvider: TDataSetProvider;
    data: OleVariant;
    ds: TClientDataSet;
begin
    tempProvider := TDataSetProvider.Create(nil);
    try
        tempProvider.DataSet := dsSource;
        data := tempProvider.Data;
    finally
        tempProvider.Free;
    end;

    ds := TClientDataSet.Create(nil);
    ds.Data := data;

    Result := ds;
end;

Что дает больший код:

var
   ds: TDataset;
   dsEditable: TDataSet;

   ds := GetDataSomeOfSomeSortFromSomewhere();

   //Clone to dataset to an in-memory dataset so we can modify it.
   dsEditable := CloneDataSet(ds);
   ds.Free;
   ds := edEditable;

   while not ds.EOF do
   begin
       ds.FieldByName('Fullname').AsString := FormatNamePrettyLike(ds.FieldByName('Fullname').AsString;
       ds.Next;
    end;

Но это выдает ошибку:

Набор данных не находится в режиме редактирования или вставки

Решение состоит в том, чтобы поставить набор данных в режиме редактирования :

//The in-memory ClientDataSet won't be editable until you mark it editable.
ds.Edit; 

///<summary>Clones a dataset into a TClientDataSet; which is an editable in-memory DataSet.</summary>
function CloneDataSet(dsSource: TDataSet): TDataSet; //TDataSet > TCustomClientDataSet > TClientDataSet
var
    tempProvider: TDataSetProvider;
    data: OleVariant;
    ds: TClientDataSet;
begin
    tempProvider := TDataSetProvider.Create(nil);
    try
        tempProvider.DataSet := dsSource;
        data := tempProvider.Data;
    finally
        tempProvider.Free;
    end;

    ds := TClientDataSet.Create(nil);
    ds.Data := data;

    //The in-memory ClientDataSet won't be editable until you mark it editable.
    ds.Edit;

    Result := ds;
end;

Повторение нормы теперь дает ошибку:

Поле Fullname не может быть изменено .

Решение состоит в том, чтобы установить для Field.ReadOnly значение false :

//Even after marking the in-memory data-set as editable, you still can't edit it 
//until you mark all fields as editable.
for i := 0 to ds.FieldCount-1 do
   ds.Fields[i].ReadOnly := False;

///<summary>Clones a dataset into a TClientDataSet; which is an editable in-memory DataSet.</summary>
function CloneDataSet(dsSource: TDataSet): TDataSet; //TDataSet > TCustomClientDataSet > TClientDataSet
var
    tempProvider: TDataSetProvider;
    data: OleVariant;
    ds: TClientDataSet;
begin
    tempProvider := TDataSetProvider.Create(nil);
    try
        tempProvider.DataSet := dsSource;
        data := tempProvider.Data;
    finally
        tempProvider.Free;
    end;

    ds := TClientDataSet.Create(nil);
    ds.Data := data;

    //The in-memory ClientDataSet won't be editable until you mark it editable.
    ds.Edit;

    //Even after marking the in-memory data-set as editable, you still can't edit it 
    //until you mark all fields as editable.
    for i := 0 to ds.FieldCount-1 do
        ds.Fields[i].ReadOnly := False;

    Result := ds;
end;

Повторение упражнения дает ошибка:

Попытка изменить поле только для чтения.

Так что я сдаюсь. Как мне отредактировать поле DataSet?

Все клонированное содержимое TCustomClientDataSet в памяти; я просто хочу отредактировать их на клиенте для отображения.

Bonus Chatter

Очевидно, я не могу добавлять новые столбцы в набор данных:

| Username | Fullname                | PrettyFullname     |
|----------|-------------------------|--------------------|
| ian      | IAN BOYD                | Ian Boyd           |
| MartynA  | MARTIN                  | Martin             |
| ngal     | NASREDDINE GALFOUT      | Nasreddine Galfout |
| uewr     | UWE RAABE               | Uwe Raabe          |

Очевидно, что я не могу присоединить обработчик событий к набору данных:

  • , так как этот обработчик данных будет недействительным, когда набор данных передается следующему человеку в цепочке (например, в потоке), и исходная форма освобождается
  • , и это также не то, что я спрашиваю; что касается изменения содержимого набора данных
  • , обновление значений затрагивает другие системы (например, базы данных, веб-службы и т. д. c). Я хочу, чтобы изменения были сделаны один раз, а затем в наборе данных

Ответы [ 2 ]

3 голосов
/ 13 марта 2020

Предполагая, что я действительно правильно понимаю ваш вопрос, он сводится к тому, чтобы изменить содержимое поля FullName на какую-то довольно отформатированную строку для отображения.

Так как вы не хотите менять фактическое содержимое поля, лучшее место для этого - в событиях OnGetText . Правильный обработчик событий для вашей задачи может выглядеть следующим образом:

procedure TMyClass.MakeFullNamePrettyGetText(Sender: TField; var Text: string; DisplayText: Boolean);
begin
  Text := FormatNamePrettyLike(Sender.AsString);
end;

Теперь вам нужно подключить этот обработчик событий к полю. Поскольку вы работаете с полями Dynami c, это нужно делать каждый раз после открытия набора данных:

qry.FieldByName('Fullname').OnGetText := MakeFullNamePrettyGetText;

Пока это происходит вне класса, в котором объявлено событие, вам необходимо ставить префикс имя события с экземпляром класса TMyClass (или как вы его называете).

1 голос
/ 13 марта 2020

Ниже приведен полностью автономный пример редактирования данных с сервера Sql с использованием ADO + TClientDataSet. Все компоненты просто сбрасываются из палитры на форму, а затем все необходимые свойства устанавливаются в коде в процедуре SetUp.

На каждом шаге я пытался использовать самый простой код для выполнения работа, чтобы не затенять элегантную простоту работы CDS + TDataSetProvider для редактирования данных. См. Метод TSqlResolver.GenUpdateSql в Provider.Pas, чтобы узнать, как он генерирует необходимые операторы Sql UPDATE для DSP, чтобы он мог обновить данные в таблице сервера. Они отправляются на сервер через специальный тип пакета данных, который DSP использует для связи между своим CDS и исходным набором данных.

Надеемся, что код не требует пояснений с минимальными комментариями.

Как вы увидите, абсолютно не нужно манипулировать атрибутами полей TF CDS. Кстати, я сделал это как приложение VCL, а не консольное, просто чтобы визуально подтвердить, что все работает, тривиально.

В качестве своего рода наименьшего общего знаменателя, который я использовал D7. В пост-Unicode Delphi поле FullName на сервере будет иметь тип NVarChar, а тип строкового поля CDS будет настраиваться автоматически.

type
  TForm1 = class(TForm)
    ADOConnection1: TADOConnection;
    ADOQuery1: TADOQuery;
    DataSource1: TDataSource;
    CDS1: TClientDataSet;
    DataSetProvider1: TDataSetProvider;
    DBGrid1: TDBGrid;
    procedure FormCreate(Sender: TObject);
  private
    procedure SetUp;
  end;
[...]
const
  scConnString = 'Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=MATest;Data Source=MAT430\ss2014';
  scCreateTable = 'create table TestTable(ID int not null primary key, FullName varchar(40))';
  scSelectAll = 'select * from TestTable';

procedure TForm1.SetUp;
begin
  AdoConnection1.ConnectionString := scConnString;

{.$define CreateTable}  // to do a one-off creation of the server table and data
{$ifdef CreateTable}
  AdoConnection1.Execute(scCreateTable);
{$endif}

  AdoQuery1.Connection := AdoConnection1;
  AdoQuery1.SQL.Text := scSelectAll;

{$ifdef CreateTable}
  AdoQuery1.Open;
  AdoQuery1.InsertRecord([1, 'Joe Blow']);
  AdoQuery1.Close;                             
{$endif}

  DataSetProvider1.DataSet := AdoQuery1;
  CDS1.ProviderName := 'DataSetProvider1';
  DataSource1.DataSet := CDS1;
  DBGrid1.DataSource := DataSource1;

  CDS1.Open;
  CDS1.Edit;
  CDS1.FieldByName('FullName').AsString := 'Mr ' + CDS1.FieldByName('FullName').AsString;
  CDS1.Post;

  //  Post the chamges back to the server table if desired
  CDS1.ApplyUpdates(0);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SetUp;
end;

end.

Добавление Одна точка, которая В комментариях была поднята возможность эффективной предварительной проверки поля FullName с помощью вычисляемого поля. Я не могу сразу придумать способ установить это в коде, но по сути я бы сделал это:

  • На CDS настройте постоянные TFields (из контекстного меню CDS).
  • В редакторе полей добавьте вычисляемое поле типа fkInternalCal c. Для CDS это лучше использовать, чем fkCalculated, потому что fkInternalCal c может быть включен в индекс CDS
  • . Делайте все необходимые вычисления в событии OnCalcFields CDS. Для выполнения расчетов не требуется обход (в вашем коде) записей CDS, потому что CDS выполняет вычисления в своих собственных обработках.

Обновление Оказывается, это Просто, если немного затруднительно, добавить поле fkInternal cal c в CDS полностью в коде. Хитрость заключается в том, чтобы получить FieldDefs с сервера, сохранить их в CDS, а затем заново создать его TFields и заново открыть его. Вот так:

  CDS1.Open;
  CDS1.StoreDefs := True;
  CDS1.Close;
  for i := 0 to CDS1.FieldDefs.Count - 1 do begin
    Field := CDS1.FieldDefs[i].CreateField(Self, Nil, CDS1.FieldDefs[i].DisplayName);
  end;
  Field := TStringField.Create(Self);
  Field.Size := CDS1.FieldByName('FullName').Size;
  Field.FieldKind := fkInternalCalc;
  Field.FieldName := 'EnhFullName';
  Field.DataSet := CDS1;

  CDS1.Open;
...