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?






Recent Comments