I realise I’ve been very quiet on the Aurelius front. Not because I’m not using it, far from it actually. I’m using it heavily with and without MVVM, on the desktop and mobile, and even on the serverside behind RAD Server. I made a vow during an ORM presentation last year to never write another line of SQL again, and while I’ve occasionally broken that vow, I don’t think I’ve written a SQL statement in Delphi code since then.
Anyway, I do plan on writing more about Aurelius, it’s on my (large) todo list, but in the meantime, I’ve had questions from multiple people that all seem to come back to the same root issue: understanding how the Aurelius TObjectManager takes responsibility for managing your object’s lifecycle.
I’ve found the best way to make this clear is to write a little test app, so here it is.
I’ve got a simple TPerson class, that looks like this:
[Entity] [AutoMapping] TPerson = class private FID: Integer; FName: String; public function ToString: string; override; property ID: Integer read FID write FID; property Name: String read FName write FName; end;
Nothing terribly surprising there if you’ve gone through my earlier Aurelius articles. The ToString overload is just to make it easy to log the object out to the screen and looks like this:
function TPerson.ToString: string; begin Result := Format('TPerson: ID=%d Name=%s', [ID, Name]); end;
After you’ve typed a name into an edit box and clicked the Create New Person button, the following code runs:
procedure TfrmMain.Button1Click(Sender: TObject); var LPerson, LPerson2 : TPerson; begin LPerson := TPerson.Create; LPerson.Name := Edit1.Text; Log('--Before Save--'); Log('LPerson is ' + LPerson.ToString); ObjMgr.Save(LPerson); Log('--After Save--'); Log('LPerson is ' + LPerson.ToString); LPerson2 := ObjMgr.Find<TPerson>(LPerson.ID); Log('--After Load--'); Log('LPerson2 is ' + LPerson2.ToString); LPerson.Name := LPerson.Name + '.1'; Log('--After Change--'); Log('LPerson is ' + LPerson.ToString); Log('LPerson2 is ' + LPerson2.ToString); ObjMgr.Update(LPerson); Log('--After Update--'); Log('LPerson is ' + LPerson.ToString); Log('LPerson2 is ' + LPerson2.ToString); end;
The Log method just adds a string to the Listbox on the form.
So, let’s have a look at the output after I add a new Person named Harvey:
There are few things to see here:
- As discussed in the earlier articles, by handing an object to the TObjectManager to save, we’re giving Aurelius responsibility to take care of that object. Hence why we don’t Free the LPerson variable later on in the code. The TObjectManager will do that.
- Before the call to TObjectManager.Save, our local TPerson reference is valid, but there is no ID value associated.
- After the call to Save, our local TPerson reference is still valid but we now have an ID. So we can continue to go on and make changes to it and call Update (which is in fact what we do further on).
- When we do a find on the same ID and store that reference in LPerson2, we’re getting back a reference to the same instance that LPerson is pointing to. Again, TObjectManager is making sure there’s only one instance of the TPerson with an ID of 8 in memory. We make a change to LPerson and we see it reflected in LPerson2. These are just two references to the same TPerson instance.
You might think this is all blindingly obvious, and if you do, great. However, where people often get tripped up is with Lists. Doing a TObjectManager.Find that returns multiple TPerson objects into a TObjectList still only gives you references in that list to the objects that the TObjectManager is managing for you. They don’t exist in two places, you don’t have to somehow keep the items in your list in sync with the items in TObjectManager: They are the same items. This is also why when you do this, you are responsible for deleting the list, but not the objects in that list: again, TObjectManager will do that.
Sometimes it is easy to forget that a TPerson variable is not the instance, it’s just a pointer to the instance. This is one scenario where it helps to remember this. The TObjectManager is the container where all my objects live, and any references I pull out of that, in local variables, Lists, whatever, are just pointers to the instances in the container.