ORM – Modeling relationships

Being able to populate data in a simple data store is great, but that is usually not good enough for any real world application. Most real world models would have some relationship between classes.

Let’s take our Note example. If you have no idea what example I’m talking about, maybe start here first.

Let’s say we needed to associate our notes to a specific case. The first step would be to modify the NoteORM unit to look like this:

unit NoteORM;
interface

uses Classes, SynCommons, mORMot;

type
  TNoteCase = class(TSQLRecord)
  private
    FDescription: RawUTF8;
  published
    property Description: RawUTF8 read FDescription write FDescription;
  end;

  TNote = class(TSQLRecord)
  private
    FBody: RawUTF8;
    FTitle: RawUTF8;
    FNoteCase: TNoteCase;
  published
    property NoteCase: TNoteCase read FNoteCase write FNoteCase;
    property Body: RawUTF8 read FBody write FBody;
    property Title: RawUTF8 index 80 read FTitle write FTitle;
  end;

implementation

end.

Here we’ve added the TNoteCase class, and added the NoteCase property to TNote. NoteCase is of type TNoteCase.

Next, we add the new TNoteCase class to our model:

RestServer := TSQLRestServerDB.CreateWithOwnModel( [NoteORM.TNote, NoteORM.TNoteCase], 'Test.DB' );

And now we can generate some data:

procedure TestIt(rest:TSQLRest);
var
  aCase : NoteORM.TNoteCase;
  aNote : NoteORM.TNote;
begin
  aCase := NoteORM.TNoteCase.Create;
  try
    aCase.Description := 'Case 1';
    rest.Add( aCase, true );
    aNote := NoteORM.TNote.Create;
    try
      aNote.Title := 'Note 1';
      aNote.Body  := 'Note 1 body. Lots of other stuff too.';
      aNote.NoteCase := aCase.AsTSQLRecord;
      rest.Add( aNote, true );

      aNote.Title := 'Note 2';
      aNote.Body  := 'Note 2 body. Lots of other stuff too. Some more things here.';
      rest.Add( aNote, true );
    finally
      aNote.Free;
    end;
  finally
    aCase.Free;
  end;
end;

The most important bit to notice is:

      aNote.NoteCase := aCase.AsTSQLRecord;

This is because mORMot stores the referential key, and not the actual instance. That means that under normal circumstances you can not access aNote.NoteCase.Description, as this will generate an Access Violation.

Once the records have been committed to the storage engine, however, there are other ways of dealing with these classes.

One way is by using the CreateJoined constructor:

procedure TestIt(rest:TSQLRest);
var
  aNote : NoteORM.TNote;
begin
  aNote := NoteORM.TNote.CreateJoined( rest, 4);
  try
    writeln( 'Case: ', aNote.NoteCase.Description );
    writeln( 'Title: ', aNote.Title );
    writeln( 'Body: ', aNote.Body );
  finally
    aNote.Free;
  end;
end;

This will auto load and populate and manage all the contained instances. This method can be slow, especially if the class graph is big.

The other option is to use lazy loading, where classes are created and managed as and when they’re required:

procedure TestIt(rest:TSQLRest);
var
  aCase : NoteORM.TNoteCase;
  aNote : NoteORM.TNote;
begin
  aNote := NoteORM.TNote.Create( rest, 4);
  try
    aCase := NoteORM.TNoteCase.Create( rest, aNote.NoteCase );
    try
      writeln( 'Case: ', aCase.Description );
      writeln( 'Title: ', aNote.Title );
      writeln( 'Body: ', aNote.Body );
    finally
      aCase.Free;
    end;
  finally
    aNote.Free;
  end;
end;

4 thoughts on “ORM – Modeling relationships

  1. In the lazy loading sample, it shows that the aNote.NoteCase can have two states in aNote, either loaded or not, so my question is how do we know if aNote.NoteCase is loaded ? Imaging, if we are passing aNote around, have ability to know that is very important.

    Like

Leave a comment