Object Pascal: TClientDataset Deletions - PullRequest
       10

Object Pascal: TClientDataset Deletions

3 голосов
/ 09 сентября 2010

Я создаю набор данных в памяти, используя TClientDataset для использования в качестве буфера приема.Добавление данных - это здорово, но как только я приступлю к обработке, я хочу иметь возможность удалить строку из набора данных.Вызов delete работает - вроде как - строка / индекс все еще доступна, но теперь не содержит действительной информации.

Это немного усложняет задачу, поскольку при обработке этого буфера не гарантируется, что записи будут удаленыфакт.Я бы предпочел не начинать сканирование буфера с первой записи и пропускать пустые элементы, так есть ли лучший способ навсегда «удалить» элемент из набора данных?Моя идея состояла в том, чтобы она работала как настоящая таблица SQL, где удаление строки не оставляет пустых записей.

Каков наилучший способ добиться этого, или я использую не тот компонент целиком?

Ответы [ 3 ]

1 голос
/ 10 сентября 2010

Что-то не так с вашим кодом. Я подготовил тестовое приложение для этого случая, поскольку через несколько дней я столкнусь с TClientDataSet в среде многопоточности. В моем тестовом приложении эта проблема отсутствует (обновление 5 для Delphi 2010)

Я опубликую этот код также в своем блоге через пару дней ... сейчас я дал его вам:

Файл 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 file:

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, чтобы избежать времени, необходимого для исследования прямо сейчас.

1 голос
/ 09 сентября 2010

По умолчанию клиентские наборы данных содержат «журнал» изменений, поскольку они также предназначены для отправки клиентских изменений на удаленный сервер, даже если они были сделаны в отключенном сеансе («модель портфеля»).Обычно этот журнал «очищается», когда вы применяете изменения к удаленной базе данных, а любые другие изменения объединяются с вашей «локальной» копией.Установите для LogChanges значение False, если оно вам не нужно, и хотите, чтобы изменения были внесены напрямую.

0 голосов
/ 09 сентября 2010

Несколько замечаний относительно вашего кода.

  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;
...