Sneak preview about DORM, The Delphi ORM

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