Making objekts persistent without use of databases.

This is a made up example in hope that someone can explain to me, how I can make objekts persistent.

 TCar = Class
   RegNr: String;
   Model : String;
   Price : integer;
 end;

 TCustomer = Class
   Nr     : integer;
   Name   : String;
   Adress : String;
   Car    : TCar; //Note the relation to another class
 end;
 
What I need to know is how to do it with; TPersistent, TFiler, streams... And/or such... But not with the use of databases.
Could someone please give me an example? (I have tried to read about it, but can't seem to get on with it). I use Delphi 4.

Thanks in advance.
Peter
retepAsked:
Who is Participating?
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.

MadshiCommented:
What do you mean with "make an object persistent"?
What exactly do you want to do?

Regards, Madshi.
0
retepAuthor Commented:
What I am hoping to do, is to save the content of each class/object to my HDD. So that I can continue my work later. I guess the big problem will be to save relations between different classes since it is impossible to know where they will be next time the file is loaded.
Of course I will not only store one single objekt, but create several objects, and while the program runs, save their adresses in a TList.

I have read something about it, and as far as I have understood it should be possible to add objects to a Stream(?) with the use of TPersistent. And thereby save the content of the objects to disk.

Hope this cleared things up, and hope U can help ;-)

Peter


0
RadlerCommented:
I haven't a good sample to give, the real code is in my home.
But taking the VCL help I saw:
function ComponentToString(Component: TComponent): string;

var
  BinStream:TMemoryStream;
  StrStream: TStringStream;
  s: string;
begin
  BinStream := TMemoryString.Create;
  try
    StrStream := TStringStream.Create(s);
    try
      BinStream.WriteComponent(Component);
      BinStream.Seek(0, soFromBeginning);
      ObjectBinaryToText(BinStream, StrStream);
      StrStream.Seek(0, soFromBeginning);
      Result:= StrStream.DataString;
    finally
      StrStram.Free;
    end;

  finally
    BinStream.Free
  end;
end;

function StringToComponent(Value: string): TComponent;
var
  StrStream:TStringStream;
  BinStream: TMemoryStream;
begin
  StrStream := TStringStream.Create(Value);
  try
    BinStream := TMemoryStream.Create;
    try
      ObjectTextToBinary(StrStream, BinStream);
      BinStream.Seek(0, soFromBeginning);
      Result := BinStream.ReadComponent(nil);

    finally
      BinStream.Free;
    end;
  finally
    StrStream.Free;
  end;
end;

A litle hint is check if the class is already registered before call ReadComponent.
The Input/Output string will be like the content of a DFM when you press <ALT>+<F12> at Delphi IDE Form.
To use this your declaration must be like:

TCar=Class( TComponent )...
TCustomer=Class( TComponent ) ...

T++, Radler.
0
Introduction to R

R is considered the predominant language for data scientist and statisticians. Learn how to use R for your own data science projects.

RBertoraCommented:
Following..
0
TimYatesCommented:
Watching Carefully...
0
KECommented:
Well, the basic streaming features of the VCL will only deal with TComponent or classes derived from TComponent. You can not add streaming capabilities to the classes you give as an example - unless you derive them from TComponent.
That is:

TCar = class(TComponent);

But I guess that you don't want to derive your simple classes from a TComponent and the overhead concerned with this. Therefore you must provide your own streaming solution. To do this, you basically write a WriteToStream and ReadFromStream procedure for each class, that takes a stream as parameter - like fx.:

procedure TCar.ReadFromStream( aStream: TStream );
begin
  with aStream do begin
    Read( );
    bla. bla. bla.
  end;
end;

This approach is not ideal, since you will have to convert any field into a typeless "pointer" to work with TStream.Read and TStream.Write.
So let's modify this to work with TReader and TWriter, which are better suited for dealing with various kinds of types.

So instead of ReadFromStream, we make a procedure that's called Read( aReader: TReader ) - like:

procedure TCar.Read( aReader: TReader );
begin
  with aReader do begin
    RegNo := ReadString;
    Model := ReadString;
    Price := ReadInteger;
  end;
end;

procedure TCar.Write( aWriter: TWriter );
begin
  with aWriter do begin
    WriteString( RegNo );
    WriteString( Model );
    WriteInteger( Price );
  end;
end;

The TReader (and TWriter) is also "remembering" the type of field it stores to the stream, so any mistakes will be caught as Stream IO error's.

TReader and TWriter is wrapped around a stream when they are created  like:

MyReader := TReader.Create( SomeStream, 4096 );

4096 indicates a buffer size (which means they are also faster to use than unbuffered streams).

What's neat about TComponent though, is their ability to "automatically" store properties declared in the Published section. When writing the solution from scratch you won't have this feature, and you will need to write every field yourself. This is not a big deal, so dont be to concerned about this.

About the relation:

Let say that you implemented the Car Read and Write methods - now do the same for Customer:

procedure TCustomer.Write( aWriter: TWriter );
begin
  with aWriter do begin
    WriteInteger( Nr );
    WriteString( Name );
    WriteString( Adress );
    Car.Write( aWriter );
  end;
end;

As you can notice, we simply write the car inside our Customer. In this case it's safe since you are only refering to the Car once !

Therefore our read method will look like this:

procedure TCustomer.Read( aReader: TReader );
begin
  with aReader do begin
    Nr := ReadInteger;
    Name := ReadString;
    Adress := ReadString;
    Car := TCar.Create;
    Car.Read( aReader );
  end;
end;

It's safe since we are only going to create the car once in this structure. Remember that it will NOT be safe to read over a previously loaded structure - since we are constructing objects inside the read method.

So what happens if we fx. need to refer to the same car from two customers. This is another question ;-)

Regards
0
sburckCommented:
listening
0
retepAuthor Commented:
Thank U all for your very good answers! Unfortunately I will have to save the relations to disk, and not just add a whole object to another object. Does anyone know a way to do that? Or is it just to difficult, so that I will have to look into databases???

Thanks for now!!! :-)
Peter
0
KECommented:
You have to be more specific.

"Save relations to disk" is a broad definition... is it the relation itself, does it include the object fields etc. ?

Regards
0
retepAuthor Commented:
Sorry, another vague explanation. What I mean is that it should save the adress to another object.
So that car : TCar; in the class TCustomer only holds the adress of a specific TCar object. And that is where the big problem comes. Because it works fine during runtime, but if you save it to disc, and reload it later, the adress of the object TCar is most likely different in mainmemory.

It would be much easier to explain, if it was possible to draw in experts-exchange. Maybe a suggestion for help-desk 8-D

Did it clear things up?
0
KECommented:
Yes, I have longed for a drawing feature several times before - I know how you feel...

However I see what you mean - references only !

There are ofcourse a billion of ways to do this, and it depends on your structure - and how you are storing, loading and building this. I can give you some clue's to implement it, and it's not as hard as it may seem like.

The easy way is to hold your objects in a list. A list has indexes and if you store and load (or build) the list the same way every time, your indexes will be a valid key for the reference.

Imagine that every car is in a list. Now instead of saving the pointer to an object you save the list-index of the object. On retrieval you do the reverse, use the index to obtain a pointer.

A more intelligent way is to make a streaming object that indexes all objects (passed to it) and insert them in an internal list belonging to the streaming object.
When the stream encounters an object it stores the object (and insert it in the list) - if the object is encountered again the stream object just saves the index.

Hope you can use it, otherwise I will need some more details preferably a small sample.

Regards
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
TOndrejCommented:
Referenced objects need to have a unique identifier, which is the exact purpose of TComponent.Name property.

An example of "persistent relation" is TUpDown.Associate property.

I suggest deriving from TComponent.
0
retepAuthor Commented:
Thx :-)

Sorry that I've been away from the question so long...
0
KECommented:
Did you get a working solution ?

Regards, and thanks
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.