My yesterday post about this busy time, have raised some interest about DORM, the Delphi ORM.
So, also if I still haven’t released any files, wish to expose some internals about DORM.
DORM is an implementation of the DataMapper design pattern written having Hibernate in mind.
It’s completely unit tested and have the following features:
- External file mapping. (JSON format)
- Persistence ignorance (every TObject can be persisted)
- Support for One-One and One-Many relations (still no many-many)
- Support for LazyLoading (you can enable§/disable lazyloading by file or by code by per-basis needs)
- Support for IdentityMap
- Support for custom “finder” (you can still use complex SQL if you want)
- Complete support for CRUD
- Transactions
- Built in logging system to log *EVERY* sql or action performed by the framework
- Opened to multiple data access strategies (interfaced based, not inheritance based) for use with different database (now I’ve developed the firebird one using DBX)
- Caching for RTTI (the TSession object have a single TRttiContext holding ALL metadata)
Code is still under heavely development.
Those are 2 test-method to show the use of DORM:
-
TPerson = class(TObject)
-
…
-
property Phones: TdormCollection…. //implements IList
-
end;
-
-
TPhone = classs(TObject)
-
…
-
end;
-
-
//and now the unit tests
-
-
procedure TTestDORMHasMany.Setup;
-
begin
-
Session := TSession.Create;
-
Session.Configure(TStreamReader.Create('dorm.conf'));
-
end;
-
-
procedure TTestDORMHasMany.TearDown;
-
begin
-
Session.Free;
-
end;
-
-
procedure TTestDORMHasMany.TestHasManyLazyLoad;
-
var
-
p: TPerson;
-
t: TPhone;
-
guid: string;
-
begin
-
p := TPerson.NewPersona; //static method. Return a fully populated TPerson object
-
try
-
t := TPhone.Create;
-
p.Phones.Add(t);
-
Session.Save(p);
-
guid := p.guid; //GUIDs, or other PK types, are generated automagically by DORM. Obviously there is a specific class loaded to do this specified in the dorm.conf file)
-
finally
-
Session.Commit;
-
end;
-
Session.StartTransaction;
-
-
// Test with lazy load ON
-
Session.SetLazyLoadFor(TypeInfo(TPerson), 'Phones', true);
-
p := Session.Load(TypeInfo(TPerson), guid) as TPerson;
-
try
-
CheckEquals(0, p.Phones.Count);
-
finally
-
Session.Commit;
-
end;
-
-
Session.StartTransaction;
-
// Test with lazy load OFF
-
Session.SetLazyLoadFor(TypeInfo(TPerson), 'Phones', false);
-
p := Session.Load(TypeInfo(TPerson), guid) as TPerson; // Without commit, AV becouse IdentityMap doesn't work properly
-
try
-
CheckEquals(1, p.Phones.Count); // Child objects are loaded
-
finally
-
Session.Commit;
-
end;
-
end;
-
-
procedure TTestDORMHasMany.TestLoadHasMany;
-
var
-
list: IList;
-
t, t1: TPhone;
-
p: TPerson;
-
guid: string;
-
begin
-
p := TPerson.NewPersona; //static method. Return a fully populated TPerson object
-
try
-
t := TPhone.Create;
-
t.Numero := '555-7765123';
-
t.Kind := 'Casa';
-
p.Phones.Add(t);
-
-
t1 := TPhone.Create;
-
t1.Number := '555-7765123';
-
t1.Kind := 'Casa';
-
p.Phones.Add(t1);
-
Session.Save(p); // save Person and Phones
-
guid := p.guid;
-
finally
-
Session.Commit;
-
end;
-
-
Session.StartTransaction;
-
p := Session.Load(TypeInfo(TPerson), guid) as TPerson;
-
try
-
CheckEquals(2, p.Phones.Count);
-
finally
-
Session.Commit;
-
end;
-
end;
Mapping, contained in a file called “dorm.conf”, is similar to the following:
-
{
-
"persistence": {
-
"database_adapter": "dorm.adapter.Firebird.TFirebirdPersistStrategy",
-
"database_connection_string":"127.0.0.1:C:\\MyProjects\\DORM\\experiments\\dorm.fdb",
-
"username": "sysdba",
-
"password":"masterkey"
-
},
-
"config": {
-
"package": "dorm.bo.Person",
-
"logger_class_name": "dorm.loggers.FileLog.TdormFileLog"
-
},
-
"mapping":
-
{
-
"TPerson":
-
{
-
"table": "people",
-
"id": {"name":"guid", "field":"guid", "field_type":"string", "size": 100, "default_value": ""},
-
"fields":[
-
{"name":"firstname", "field":"first_name", "field_type":"string", "size": 100},
-
{"name":"lastname", "field":"last_name", "field_type":"string", "size": 100},
-
{"name":"age", "field":"age", "field_type":"integer"},
-
{"name":"borndate", "field":"born_date", "field_type":"date"}
-
],
-
"has_many":[{
-
"name": "Phones",
-
"class_name":"TPhone",
-
"child_field_name":"guid_person",
-
"lazy_load": false
-
}],
-
"has_one": {
-
"name": "car",
-
"class_name":"TCar",
-
"child_field_name":"guid_person"
-
}
-
},
-
"TPhone":
-
{
-
"table": "phones",
-
"id": {"name":"guid", "field":"guid", "field_type":"string", "size": 100, "default_value": ""},
-
"fields":[
-
{"name":"number", "field":"number", "field_type":"string", "size": 100},
-
{"name":"kind", "field":"kind", "field_type":"string", "size": 100},
-
{"name":"guid_person", "field":"guid_person", "field_type":"string", "size": 100}
-
]
-
}
-
}
-
}
The PODO (Plain Old Delphi Objects) can be binded to the VCL controls with a set of MediatingView (Model-GUI-Mediator Pattern) with an Observer mechanism to mantain things in synch.
Any comments? Someone interested?
24 Responses to “Sneak preview about DORM, The Delphi ORM”
Leave a Reply
You must be logged in to post a comment.

September 1st, 2010 at 9:57 am
Looks good and promising! Would really like a good ORM for Delphi!
Some initial comments/questions:
- why Session.Commit in finally, no initial Session.StartTransaction?
- how about generics? like “p := Session.Load” is much nicer to read and to code!
- how about attributes?
[Table = 'people']
class TPerson
[LayLoad=True]
property Phones: TdormCollection
I prefer attributes instead of external mapping, would be nice to have both to be flexible whatever anyone likes
- how about a class generator from database? My current customer does this with their own data objects, and it is really useful with many tables and many db changes!
I hope you develop with above points in mind, even if this is limited to D2010+, otherwise your DORM will be old and outdated before it is released…
I know there are a lot D7 programmers and apps, but after working a half year with D2010 I will never want back to old and outdated D7!
IMHO, Delphi needs a modern(!) ORM to compete with .Net, not an old legacy Delphi compatible thing which does not make use of latest technologies (so it is not easy to use)
I also saw some partial LINQ implementation with generics in DeHL!
September 1st, 2010 at 10:07 am
I am interested. Is it open source with SVN?
September 1st, 2010 at 10:29 am
@André:
Q: why Session.Commit in finally, no initial Session.StartTransaction?
A: Create a Session imply a new transaction start, so needs the commit.
Q: how about generics? like “p := Session.Load” is much nicer to read and to code!(3);”
A: Yes, is under consideration. Could be something like the following
“P:= Session.Load
Q: how about attributes?
[Table = 'people']
class TPerson
[LayLoad=True]
property Phones: TdormCollection
I prefer attributes instead of external mapping, would be nice to have both to be flexible whatever anyone likes
A: Attributes are nice but are limited to simple mapping. How you can mapp 3 object in a simgle table with attributes mapping? How map a single object in 3 tables? External file mapping is very powerfull and extendible. However, Attributes could be used for simple mapping.
Q: how about a class generator from database? My current customer does this with their own data objects, and it is really useful with many tables and many db changes!
A: Already in the roadmap. The start point for the DORM will be always the mapping file. But that file can be generated from DB and can generate the Delphi code.
I REALLY want a modern ORM. So I need the NEW RTTI. With the new RTTI is possibile to write a decent and modern ORM in a reasonable time. SOuld be very nice if some programmer (hopefully with Hibernate/NHibernate exterience could help me in this effort).
ASAP I’ll post other details.
September 1st, 2010 at 10:38 am
@Gad D Lord:
Still there arent a public repository. ASAP I’ll setup a googlecode project for DORM
September 1st, 2010 at 10:53 am
- session.create is implicit startransaction? Wouldn’t it be better to name it “session.CreateAndStartTransaction”? otherwise everybody will forget to commit!
- for my customer I need simple mapping
so attributes would be good enough (better than seperate file!) but for complex mapping you need mapping file yes.
Also think about version control: because I heard this is a (very) weak point of the ADO.Net ORM: 3 seperated xml files for mapping with bad version control possibilities (merge, compare, etc)
I would like to help, but I have no no hibernate experience and little spare time (maybe more in the winter :-). But can help thinking and try some techniques (I will refactor my customers data objects in near future, so maybe there is some things we can share)
And yes, you will need the new RTTI for this! Very nice new feature!
September 1st, 2010 at 11:01 am
@André
Ok, if you can we could collaborate for DORM.
I’ll contact you ASAP.
My contacts are here http://www.danieleteti.it/?page_id=241
September 1st, 2010 at 12:21 pm
Daniele,
the DORM code is only Delphi 2010 compliant … or not?.
luca
D7 lover
September 1st, 2010 at 12:38 pm
Looks really nice!
Ill second use of attributes/rtti instead of external file.
Have You thought about something like this:
if t.Numero = null then…
in my “ORM” object fields are objects not simple type fields, so i can do p.Numer.IsNull…
September 1st, 2010 at 12:45 pm
@Luca
DORM is D2010+ compatible. The new RTTI introduced in D2010 is a necessary feature for DORM.
September 1st, 2010 at 1:26 pm
Looks promising!
One question: Is lazy-load restricted to TdormCollection or any association can be lazy-load?
September 1st, 2010 at 1:34 pm
@Magno Machado Paulo
lazy-load is not restricted to TdormCollection.
Now there is only this possibility, but the lazyloading is designed for using in *EVERY* association.
September 1st, 2010 at 1:40 pm
@misiak:
Nullable types should be a good thing for Delphi. DORM can use every value-type-compatible property from the RTTI POV.
There is an alternative way to dealing with null values: using a default value for the value. This “null-value” is configured in the dorm.conf file. So your code could become:
If t.numero = -1 then … //-1 is configured as “NULL” value for the field “NUMERO”
Is not elegant as nullable-types bnut i works.
September 1st, 2010 at 3:35 pm
DORM is a wonderful name for a persistence framework, it even beats Hibernate
September 2nd, 2010 at 2:32 am
Do you have the code available on CodePlex ? SourceForge or GitHub, etc etc ?
Keen to give the beast a run..!
September 2nd, 2010 at 7:28 am
Looks very promising. One question though: why don’t you use an xml file configuration for dorm.conf? Why re invent the wheel?
September 2nd, 2010 at 9:35 am
@Andrew Tierney:
No, still there isn’t a public package, Probably I’ll put the project in googlecode.
@justin:
JSON is a standard, it is less verbose than XML and it is more fast to parse. There are opensource parser for it and there is the Delphi 2010 one. So, I dont reinvent the wheel… I’ve only “changed” the usually wheel J
September 7th, 2010 at 3:44 am
How does this compare to http://sourceforge.net/projects/larryhengensopf/ ?
–jeroen
September 7th, 2010 at 1:59 pm
@Jeroen Pluimers:
* DORM haven’t support for design time stuff. It is my choice.
* hOPF AFAIK require a LayerSupertype (http://martinfowler.com/eaaCatalog/layerSupertype.html), DORM dont.
* DORM haven’t a built-in “binding” system. There is an external framework based on the MGM (Model-GUI-Mediator) design pattern that can do that.
* DORM is *HEAVELY* based upen interfaces, AFAIK hOPF dont.
* hOPF is ccompatible with old versions, DORM require D2010+
* hOPF if a finished project with years of history, currently DORM is a *nice* working-experiment.
My knowledge about hOPF could be not sufficient to do a deeper comparison.
September 18th, 2010 at 1:12 pm
Can u post a link to the beta code of DORM?
September 20th, 2010 at 3:01 pm
Hi Daniele,
really a very interesting work.
Thank you very much
December 28th, 2010 at 10:03 pm
How did you solve the LazyLoading problem, if Delphi does not have the facility of dynamic proxy?
December 29th, 2010 at 12:40 pm
@Lucas (Brasil)
LazyLoading is a desin pattern. It is not tied to a specific language feature.
http://en.wikipedia.org/wiki/Lazy_loading
Check the code for an example.
January 13th, 2011 at 12:09 am
What a way to implement the design pattern LazyLoad did you use?
Virtual proxy, Ghost, Value holder?
Many thanks.
November 2nd, 2011 at 5:50 pm
[...] begins as a my personal project more than one year ago, and now is sponsored by bitTime Software that offers custom development, consultancy, mentoring [...]