Object Pascal: TClientDataset Deletions

3
我正在使用TClientDataset创建一个内存数据集作为接收缓冲区。添加数据很好,但一旦我开始处理它,就希望能够从数据集中删除该行。调用delete函数可以实现 - 有点 - 该行/索引仍然可访问,但不包含有效信息。
这使得事情有点困难,因为在处理此缓冲区时不能保证条目将被删除。我不想从第一项开始扫描缓冲区并跳过空项,所以是否有更好的方法可以永久性地“删除”数据集中的项目?我的想法是它应该像实际的SQL表格一样工作,其中删除行不会留下空记录。
如何实现最佳方法,或者我完全使用了错误的组件?

1
你能否编辑你的问题并提供一个代码示例来演示这个问题?每次我使用Delete时,它都会完全删除该行。 - Mason Wheeler
我在两个线程中使用该对象,一个用于填充它,另一个用于处理和删除它。我在每个线程中都使用了TCriticalSection锁。以下是一些代码:http://pastebin.com/FatuYEwa。现在,在try/finally块中的部分在计时器中执行,第一次执行时它可以完美地工作。然而第二次,在调用MyDataset.First之后,它将指向一个不包含有效数据的条目。根据其数据类型,所有字段都为空(整数为0,字符串为空字符串等)。此外,RecordCount包含相同数量的条目。 - Diego
这不是你的问题,但看着你的代码,你错过了被删除记录旁边的记录。你的.Next语句应该在else语句中,以避免这种错误行为。 - jachguate
3个回答

1

默认情况下,客户端数据集会维护一个“更改日志”,因为它们也被设计成能够将客户端更改发送到远程服务器,即使是在断开的会话中(“公文包模型”)。通常,在将更改应用于远程数据库时,此日志会被“清除”,并且任何其他更改都会与您的“本地”副本合并。 如果不需要更改日志并希望直接进行更改,请将LogChanges设置为False。


我尝试了这个方法,但结果还是一样。就好像删除操作只是用空值或其他东西覆盖了我的条目。 - Diego

1

你的代码出了些问题。我为这种情况准备了一个测试应用程序,因为我将在几天内在多线程环境中面对TClientDataSet。我的测试案例应用程序没有出现这个问题(Delphi 2010更新5)

我将在几天后在自己的博客中发布这个代码...目前我给了你:

DFM文件:

object Form2: TForm2
  Left = 0
  Top = 0
  Caption = 'Form2'
  ClientHeight = 337
  ClientWidth = 635
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnClose = FormClose
  PixelsPerInch = 96
  TextHeight = 13
  object Memo1: TMemo
    Left = 8
    Top = 8
    Width = 257
    Height = 321
    Lines.Strings = (
      'Memo1')
    TabOrder = 0
  end
  object Button1: TButton
    Left = 271
    Top = 8
    Width = 170
    Height = 25
    Caption = 'Start'
    TabOrder = 1
    OnClick = Button1Click
  end
  object cdsTest: TClientDataSet
    Aggregates = <>
    Params = <>
    Left = 584
    Top = 32
    object cdsTestNumber: TIntegerField
      FieldName = 'Number'
    end
  end
  object tToMemo: TTimer
    Enabled = False
    Interval = 500
    OnTimer = tToMemoTimer
    Left = 376
    Top = 144
  end
end

pas文件:

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, DB, DBClient, SyncObjs, ExtCtrls;

type
  TWriterThread = class(TThread)
  private
    FDataSet: TClientDataSet;
    //FWriteLock: TMultiReadExclusiveWriteSynchronizer;
    FLock: TCriticalSection;
  public
    constructor Create(ADataSet: TClientDataSet; ALock: TCriticalSection);
    procedure Execute; override;
  end;

  TDeleterThread = class(TThread)
  private
    FDataSet: TClientDataSet;
    //FWriteLock: TMultiReadExclusiveWriteSynchronizer;
    FLock: TCriticalSection;
  public
    constructor Create(ADataSet: TClientDataSet; ALock: TCriticalSection);
    procedure Execute; override;
  end;

  TForm2 = class(TForm)
    cdsTest: TClientDataSet;
    Memo1: TMemo;
    cdsTestNumber: TIntegerField;
    Button1: TButton;
    tToMemo: TTimer;
    procedure Button1Click(Sender: TObject);
    procedure tToMemoTimer(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
    FLock: TCriticalSection;
    FWriterThread: TWriterThread;
    FDeleterThread: TDeleterThread;
    procedure cdsToMemo;
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);
begin
  Button1.Enabled := False;
  cdsTest.CreateDataSet;
  cdsTest.LogChanges := False;
  FLock := TCriticalSection.Create;
  tToMemo.Enabled := True;
  FWriterThread := TWriterThread.Create(cdsTest, FLock);
  FDeleterThread := TDeleterThread.Create(cdsTest, FLock);
end;

{ TWriterThread }

constructor TWriterThread.Create(ADataSet: TClientDataSet;
  ALock: TCriticalSection);
begin
  inherited Create(False);
  FDataSet := ADataSet;
  FLock := ALock;
end;

procedure TWriterThread.Execute;
var
  I: Integer;
begin
  inherited;
  I := 0;
  while not Terminated do
  begin
    FLock.Enter;
    try
      Inc(I);
      FDataSet.AppendRecord([I]);
    finally
      FLock.Leave;
    end;
    Sleep(500);  //a new record aproximately each half second
  end;
end;

{ TDeleterThread }

constructor TDeleterThread.Create(ADataSet: TClientDataSet;
  ALock: TCriticalSection);
begin
  inherited Create(False);
  FDataSet := ADataSet;
  FLock := ALock;
end;

procedure TDeleterThread.Execute;
const
  MaxRecords = 100;
var
  ProcessedRecords: Integer;
begin
  inherited;
  while not Terminated do
  begin
    Sleep(3000);  //delete records aproximately every 3 seconds
    FLock.Enter;
    try
      FDataSet.First;
      ProcessedRecords := 0;
      while (not FDataSet.Eof) and (ProcessedRecords < MaxRecords) do
      begin
        Inc(ProcessedRecords);
        if Odd(FDataSet.Fields[0].AsInteger) then
          FDataSet.Delete
        else
          FDataSet.Next;
      end;
    finally
      FLock.Leave;
    end;
  end;
end;

procedure TForm2.cdsToMemo;
begin
  FLock.Enter;
  try
    Memo1.Lines.BeginUpdate;
    try
      Memo1.Lines.Clear;
      cdsTest.First;
      while not cdsTest.Eof do
      begin
        Memo1.Lines.Add(cdsTestNumber.AsString);
        cdsTest.Next;
      end;
    finally
      Memo1.Lines.EndUpdate;
    end;
  finally
    FLock.Leave;
  end;
end;

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  tToMemo.Enabled := False;
  if cdsTest.Active then
  begin
    FDeleterThread.Terminate;
    FDeleterThread.WaitFor;
    FWriterThread.Terminate;
    FWriterThread.WaitFor;
  end;
end;

procedure TForm2.tToMemoTimer(Sender: TObject);
begin
  tToMemo.Enabled := False;
  cdsToMemo;
  tToMemo.Enabled := True;
end;

end.

我不会再发更多的解释了,因为您似乎对多线程编程很熟悉。如果您有任何疑问,请随时在评论中提出。

只有一件事...我原本计划使用TMultiReadExclusiveWriteSynchronizer来实现更好的并发性,但我没有将ReadAccess提升为WriteAccess的经验,因此使用了CriticalSection来避免现在需要调查的时间。


0

关于你的代码,我有几点评论:

  1. 你使用了一种不寻常的方式来循环遍历数据集(使用计数器并仍然使用next)。

  2. 在删除时,我的首选方向是从末尾开始。

  3. 你没有在删除后发布你的数据集。

我的建议是尝试类似以下的代码:

MyDataSet.RecNo:= 99
while not MyDataSet.Bof do
begin
  fD1 := MyDataset.FieldByName('Field1').AsInteger;
  fD2 := MyDataset.FieldByName('Field2').AsInteger;
  fD3 := MyDataset.FieldByName('Field3').AsInteger;

  if someCondition then
    MyDataset.Delete;

  MyDataSet.Post;

  MyDataset.Previous;
end;

3
在删除数据集后调用Post会引发异常:EDatabaseError: 无法在关闭的数据集上执行此操作 - Mason Wheeler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接