如何编辑数据集?

Posted

技术标签:

【中文标题】如何编辑数据集?【英文标题】:How to edit Dataset? 【发布时间】:2020-06-24 21:29:11 【问题描述】:

短版

我怎么称呼:

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 字段在内存中,然后再用它做其他事情(即它永远不会回到数据库中,我不知道它来自哪里from ,它可能不是来自数据库):

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

尝试修改字段会出现异常:

Dataset not in edit or insert mode

解决方案是将数据集克隆到内存中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;

但这给出了错误:

Dataset not in edit or insert mode

解决办法是put the dataset in edit mode:

//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不能修改。

解决办法是set Field.ReadOnly to 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;

重复练习会出现错误:

试图修改只读字段。

所以我放弃了。如何编辑数据集字段?

克隆的内存中的TCustomClientDataSet 内容都在那里;我只是想在客户端上编辑它们以供显示。

奖金聊天

显然我不能向数据集添加新列:

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

显然我无法将事件处理程序附加到数据集:

因为当数据集传递给链中的下一个人(例如线程)时,该数据处理程序将无效,并释放原始表单 这也不是我要问的;这是关于修改数据集的内容 值的更新会影响其他系统(例如数据库、Web 服务等)。我希望更改完成一次,然后进入数据集

【问题讨论】:

您应该调用 DataSet.Edit 将数据集置于 dsEdit 状态,然后再尝试修改字段,然后调用 DataSet.Post 将当前记录的更改发布回数据集,然后再移动到下一个。实际上,移动数据集光标(通过调用 Next、Prior、First 等)将隐式发布任何待处理的更改,但最好不要依赖于此。最好在表单/数据模块上使用 DSP 和 CDS 组件,因为它可能更容易找到您的问题。在 CDS 中编辑数据应该“正常工作”,除非 [继续] 您的 DSP 正在从只读源获取数据。在您的位置上,我会将事情归结为一个最小的示例并对其进行调试 - 它实际上不应该超过大约 10 行代码,而不是您似乎拥有的熨平板。顺便说一句,如果 DSP 源是 Sql 数据集,则 DSP 足够智能,可以生成用于发出 UPDATE 语句的代码以强制执行更改,即使源名义上是只读查询结果。 PS 不要忘记在 CDS 上调用 ApplyUpdates,否则它们不会。 @MartynA 这不是 OP 所要求的。他说这些名字不会被发布到数据库中。他想修改它们以供展示。 我认为@Sertac 的重点在于,如果您只是想对FullName 进行美化演示以用于显示目的,请简化将 fkInternalCalc 字段添加到 CDS 并将其值格式化为您想要在OnCalcFields 事件中 - 那么,根本不需要在代码中遍历 CDS,这一切都在幕后完成。但是当然可能可以编辑 CDS 中的记录,这就是它们的全部意义所在。并且 DSP 提供了一种将更改反馈到底层数据集的方法即使如果它是从提供只读结果集的 SQL 查询中获得的。 【参考方案1】:

假设我实际上正确理解了您的问题,归结为将 FullName 的字段内容更改为一些格式化的字符串以供显示。

因此,由于您不想更改实际的字段内容,因此最好在字段 OnGetText 事件中执行此操作。适合您的任务的事件处理程序可能如下所示:

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

现在您必须将该事件处理程序连接到该字段。当您使用动态字段时,每次打开数据集后都必须这样做:

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

只要这发生在声明事件的类之外,您就需要在事件名称前加上 TMyClass 的类实例(或任何您可以称呼它的名称)。

【讨论】:

【参考方案2】:

下面是一个完全独立的从 Sql Server 编辑数据的示例 使用 ADO + TClientDataSet。所有组件都简单地从调色板中删除 到表单上,然后在SetUp 过程中的代码中设置所有必要的属性。

在每一步,我都尝试使用最简单的代码来完成这项工作,以免混淆 CDS + TDataSetProvider 如何编辑数据的优雅简单。请参阅 Provider.Pas 中的 TSqlResolver.GenUpdateSql 方法以了解如何 它为 DSP 生成必要的 Sql UPDATE 语句以发出更新 服务器表中的数据。这些通过特殊类型发送到服务器 DSP 用于在其 CDS 和源数据集之间进行通信的数据包的数量。

希望代码能够以最少的 cmets 自我解释。

正如您将看到的,绝对不要摆弄 CDS 的 TFields 的属性 有必要的。顺便说一句,我将其作为 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.

附录 在 cmets 中提出的一点是使用计算字段有效美化 FullName 字段的可能性。我无法立即想到在代码中设置它的方法 但基本上我会这样做:

在 CDS 上,设置持久 TField(从 CDS 的上下文菜单中)。 在字段编辑器中,添加一个 fkInternalCalc 类型的计算字段。对于 CDS,这 比 fkCalculated 更好用,因为 fkI​​nternalCalc 可以包含在 CDS 的索引中 在 CDS 的OnCalcFields 事件中进行任何必要的计算。没有遍历 (在您的代码中)CDS 记录是进行计算所必需的,因为 CDS 在其自己的机制中进行计算。

更新 事实证明,完全在代码中将 fkInternal calc 字段添加到 CDS 是很简单的,如果有点复杂的话。诀窍是从服务器检索 FieldDef,将它们保存在 CDS 中,然后重新创建其 TField 并重新打开它。像这样:

  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;

【讨论】:

您能否在最初查询数据后尝试关闭和释放数据库连接 (AdoConnection1)。同时关闭并释放您的 ADOQuery (AdoQuery)。似乎某些操作会默默地尝试将数据更新回数据源中。完全破坏数据库连接将消除数据在任何地方更新的可能性。 我认为我不需要:只需将 DSP 的 DataSet 属性设置为 Nil 即可将其与 AdoQuery 断开连接,从而消除将更改发送回服务器的任何可能性。或者你可以简单地做 CDS2.Data := CDS1.Data (CDS2 是一个独立的,没有任何 DSP),然后在 CDS2 上做你的工作。 哦,我希望不言而喻,如果您想在任何编辑之前保留 CDS1 数据的副本,您可以在编辑之前简单地执行 CDS2.Data := CDS1.Data。顺便说一句,在 CDS2 拾取包含在 CDS1.Data 中的 Midas 数据包中的所有字段定义等之前,不需要初始化 CDS2;

以上是关于如何编辑数据集?的主要内容,如果未能解决你的问题,请参考以下文章

如何构建 Rails 视图以编辑关联的数据集

3.4 主机画面编辑

las数据集加载las数据

交互式可编辑草图数据集DIDI dataset: Digital Ink Diagram data

交互式可编辑草图数据集DIDI dataset: Digital Ink Diagram data

编辑 xml Annotations 数据集的标签