Writing a TClientDataSet to file using TStream

I want to write to a file using TStream, but cannot crack the syntax required:

Here is the code to create the stream and load the cds file:
FileName := SysDir + 'System.cds';
StreamSystem := TFileStream.create(FileName, mode);
cdsSystem.LoadFromStream(streamSystem);

Open in new window

Having done the updates, I want to save the file, via the Stream:
cdsSystem.SaveToStream(StreamSystem);
StreamSystem.Write(*buffer, sizeof(StreamSystem));

Open in new window

However, I have no idea what should be used as the *buffer variable.

I know I can save as a file, but I want to make the stream logic work. If you can help with that problem, please help!
GrahamDLovellAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

jimyXCommented:
> StreamSystem.Write(*buffer, sizeof(StreamSystem));
This is totally unnecessary.

You got your data saved by this line only:
cdsSystem.SaveToStream(StreamSystem);

Here is a link if you need to see "FS.Write" working example.
0
jimyXCommented:
Assuming you have saved the cdsSystem to a MemoryStream "Stream2" and you need to save that stream to the FileStream "StreamSystem", what you will be doing is using "CopyFrom":

cdsSystem.SaveToStream(Stream2);
Stream2.Position:= 0;
StreamSystem.CopyFrom(Stream2, Stream2.Size);

Open in new window

0
GrahamDLovellAuthor Commented:
When I have tested it, the line of code
cdsSystem.SaveToStream(StreamSystem);

Open in new window

does not change anything on the disk.

I was aware of your link, but it doesn't help me in resolving what to put in place of *buffer in the case of a TClientDataSet TStream. That is why I asked the question.
0
Rowby Goren Makes an Impact on Screen and Online

Learn about longtime user Rowby Goren and his great contributions to the site. We explore his method for posing questions that are likely to yield a solution, and take a look at how his career transformed from a Hollywood writer to a website entrepreneur.

MerijnBSr. Software EngineerCommented:
Can you show some more code? What type is cdsSsytem?
0
GrahamDLovellAuthor Commented:
Thanks.

Here is a little program I have written to demonstrate the problem:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Data.DB, Datasnap.DBClient;

type
  TForm1 = class(TForm)
    cdsSystem: TClientDataSet;
    Button1: TButton;
    procedure FormActivate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    mode: Word;       {Stream read Mode}
    StreamSystem: TStream;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  cdsSystem.SaveToStream(StreamSystem);
  {StreamSystem.Write ????}
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  mode := fmOpenRead or fmShareDenyWrite;
  StreamSystem := TFileStream.create('system.cds', mode);
  with cdsSystem do
  begin
    LoadFromStream(streamSystem);
    insert;
    FieldByName('Data').AsString := 'Test Value';
    post;
  end;

end;

end.

Open in new window


The file is not actually written by the SaveToStream command.

What am I doing that is wrong?
0
GrahamDLovellAuthor Commented:
There is nothing like writing sample code to highlight the issue:

I have changed the command from:
mode := fmOpenRead or fmShareDenyWrite;

Open in new window

to
mode := fmOpenReadWrite or fmShareDenyWrite;

Open in new window


It now saves the file (it updates the time-stamp on the disk), but the new row is not included in the "changed" file. The original file is just re-written.

Is the time-stamp change just related to the release of the lock on the file?
0
jimyXCommented:
> It now saves the file (it updates the time-stamp on the disk), but the new row is not included in the "changed" file. The original file is just re-written.

Every time you access the file, and make changes, time-stamp gets updated.
Working with files is very easy once you know how to work with.
For instance every time you call  "StreamSystem:= TFileStream.Create('ur file', fmOpenReadWrite)" and start writing by calling "cdsSystem.SaveToStream(StreamSystem);" you get your old data overwritten. Because the file is opened and the position of writing is at the beginning of the file. In case you want to append some data then you have to move to the end of the file "StreamSystem.Position:= StreamSystem.Size;" before writing. Also you write what you have carried in the "ClientDataSet" when calling "cdsSystem.SaveToStream(StreamSystem);", no matter what you have updated or what you put earlier. If you need to save only the new row, then have the new row written by overwriting the entire file (bad if the data is huge), or get that new row in the ClientDataSet and move to the end of the file and save to the stream. But more easire if you utilized the DataBase functionality of the ClientDataSet where the data is bound to the file and you can carry the updates only to the file without saving only new data or all data.

procedure TForm1.Button1Click(Sender: TObject);
begin
  //set the position to the end of the stream, so new coming data append whatever we saved before, instead of overwriting.
  StreamSystem.Position:= StreamSystem.Size;

  //start writing the cdsSystem data to the stream, if you did not move to the end
  //of the stream you overwrite whatever beyond the current position.
  cdsSystem.SaveToStream(StreamSystem);
  {StreamSystem.Write ????} // no need trust me
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  mode := fmOpenRead or fmShareDenyWrite; //You need fmOpenReadWrite if you intend to write and fmCreate if the file does not exist.
  StreamSystem := TFileStream.create('system.cds', mode);
  with cdsSystem do
  begin
    LoadFromStream(streamSystem);
    insert;
    FieldByName('Data').AsString := 'Test Value';
    post;
  end;

Open in new window


So it all depends on what you want to do.

> Is the time-stamp change just related to the release of the lock on the file?
Yes, if you modified the file, the moment you release the FileStream "StreamSystem.free" the modified time-stamp is updated.

PS: Do not forget to free the created resources "StreamSystem.free;" when you are done.
0
GrahamDLovellAuthor Commented:
Thanks JimyX for the information - it provided the clue that helped me to solve this long-standing problem.

I don't know about other uses of SaveToStream, but in this case the position of the starting pointer is at the end of the file before you execute the command. I confirmed this with the debugger.

So the fix to my problem was just one line of code:   StreamSystem.Position := 0;

The new, fully functional program is as follows:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Data.DB, Datasnap.DBClient;

type
  TForm1 = class(TForm)
    cdsSystem: TClientDataSet;
    Button1: TButton;
    procedure FormActivate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    mode: Word;       {Stream read Mode}
    StreamSystem: TStream;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  StreamSystem.Position := 0;
  cdsSystem.SaveToStream(StreamSystem);
  cdsSystem.Close;
  StreamSystem.Free;
  close;
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  mode := fmOpenReadWrite or fmShareDenyWrite;
  StreamSystem := TFileStream.create('system.cds', mode);
  with cdsSystem do
  begin
    LoadFromStream(streamSystem);
    insert;
    FieldByName('Data').AsString := 'Test Value';
    post;
  end;

end;

end.

Open in new window


When you are saving to a file, rather than linking to a database, you have to save the whole file at once. It will just put unreadable data at the end of file (which was what was happening before).

The advantage of using files, rather than a more conventional database parent, is that it provides a very light weight database system in a single user environment. The disadvantage is that it could take longer to save the changes, and that changes will not be saved if the program uncontrollably crashes before saving.

In fact, I have half a dozen tables, a couple with around 10000 rows, and the time taken to save these tables is imperceptible.

I use TStream rather than simply using LoadFromFile. This is needed to cover the situation where I have two programs in which a single user can access the one table. TStream permissions stop the user from accidentally updating the table when the other program has it open.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
jimyXCommented:
Glad it's working for you as you wanted.
0
GrahamDLovellAuthor Commented:
Thanks for your help in solving this problem
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.