Sneak preview about DORM, The Delphi ORM
· 3 min read
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?
Comments
comments powered by Disqus