3

我正在使用 TClientDataset 创建一个内存数据集以用作接收缓冲区。添加数据很棒,但是一旦我开始处理它,我希望能够从数据集中删除该行。调用删除工作 - 有点 - 行/索引仍然可以访问,但现在不包含有效信息。

这让事情变得有点困难,因为当我处理这个缓冲区时,不能保证条目实际上会被删除。我宁愿不从第一个条目开始扫描缓冲区并跳过空项目,那么有没有更好的方法从数据集中永久“删除”项目?我的想法是它应该像一个实际的 SQL 表一样工作,其中删除一行不会留下空记录。

实现这一目标的最佳方法是什么,还是我完全使用了错误的组件?

4

3 回答 3

1

你的代码有问题。我为这个案例准备了一个测试应用程序,因为几天后我将在多线程环境中面对 TClientDataSet。我的测试用例应用程序没有出现这个问题(Delphi 2010 Update 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

文件:

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 来避免现在调查所需的时间。

于 2010-09-10T03:09:11.150 回答
1

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

于 2010-09-09T13:46:51.757 回答
0

关于您的代码的几点说明。

  1. 您正在使用一种不寻常的方式来遍历您的数据集(使用计数器并仍在使用下一个)。

  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;
于 2010-09-09T15:00:40.823 回答