Sneak preview about DORM, The Delphi ORM

Design Patterns, Programming, RTTI Add comments

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:

  1. TPerson = class(TObject)
  2. property Phones: TdormCollection…. //implements IList
  3. end;
  4.  
  5. TPhone = classs(TObject)
  6. end;
  7.  
  8. //and now the unit tests
  9.  
  10. procedure TTestDORMHasMany.Setup;
  11. begin
  12.   Session := TSession.Create;
  13.   Session.Configure(TStreamReader.Create('dorm.conf'));
  14. end;
  15.  
  16. procedure TTestDORMHasMany.TearDown;
  17. begin
  18.   Session.Free;
  19. end;
  20.  
  21. procedure TTestDORMHasMany.TestHasManyLazyLoad;
  22. var
  23.   p: TPerson;
  24.   t: TPhone;
  25.   guid: string;
  26. begin
  27.   p := TPerson.NewPersona;  //static method. Return a fully populated TPerson object
  28.   try
  29.     t := TPhone.Create;
  30.     p.Phones.Add(t);
  31.     Session.Save(p);
  32.     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)
  33.   finally
  34.     Session.Commit;
  35.   end;
  36.   Session.StartTransaction;
  37.  
  38.   // Test with lazy load ON
  39.   Session.SetLazyLoadFor(TypeInfo(TPerson), 'Phones', true);
  40.   p := Session.Load(TypeInfo(TPerson), guid) as TPerson;
  41.   try
  42.     CheckEquals(0, p.Phones.Count);
  43.   finally
  44.     Session.Commit;
  45.   end;
  46.  
  47.   Session.StartTransaction;
  48.   // Test with lazy load OFF
  49.   Session.SetLazyLoadFor(TypeInfo(TPerson), 'Phones', false);
  50.   p := Session.Load(TypeInfo(TPerson), guid) as TPerson; // Without commit, AV becouse IdentityMap doesn't work properly
  51.   try
  52.     CheckEquals(1, p.Phones.Count); // Child objects are loaded
  53.   finally
  54.     Session.Commit;
  55.   end;
  56. end;
  57.  
  58. procedure TTestDORMHasMany.TestLoadHasMany;
  59. var
  60.   list: IList;
  61.   t, t1: TPhone;
  62.   p: TPerson;
  63.   guid: string;
  64. begin
  65.   p := TPerson.NewPersona;  //static method. Return a fully populated TPerson object
  66.   try
  67.     t := TPhone.Create;
  68.     t.Numero := '555-7765123';
  69.     t.Kind := 'Casa';
  70.     p.Phones.Add(t);
  71.  
  72.     t1 := TPhone.Create;
  73.     t1.Number := '555-7765123';
  74.     t1.Kind := 'Casa';
  75.     p.Phones.Add(t1);
  76.     Session.Save(p); // save Person and Phones
  77.     guid := p.guid;
  78.   finally
  79.     Session.Commit;
  80.   end;
  81.  
  82.   Session.StartTransaction;
  83.   p := Session.Load(TypeInfo(TPerson), guid) as TPerson;
  84.   try
  85.     CheckEquals(2, p.Phones.Count);
  86.   finally
  87.     Session.Commit;
  88.   end;
  89. end;

Mapping, contained in a file called “dorm.conf”, is similar to the following:

  1. {
  2.   "persistence": {
  3.     "database_adapter": "dorm.adapter.Firebird.TFirebirdPersistStrategy",
  4.     "database_connection_string":"127.0.0.1:C:\\MyProjects\\DORM\\experiments\\dorm.fdb",
  5.     "username": "sysdba",
  6.     "password":"masterkey"
  7.     },
  8.   "config": {
  9.     "package": "dorm.bo.Person",
  10.     "logger_class_name": "dorm.loggers.FileLog.TdormFileLog"
  11.   },      
  12.   "mapping":
  13.     {      
  14.       "TPerson":
  15.       {
  16.         "table": "people",
  17.         "id": {"name":"guid", "field":"guid", "field_type":"string", "size": 100, "default_value": ""},
  18.         "fields":[
  19.           {"name":"firstname", "field":"first_name", "field_type":"string", "size": 100},
  20.           {"name":"lastname", "field":"last_name", "field_type":"string", "size": 100},
  21.           {"name":"age", "field":"age", "field_type":"integer"},
  22.           {"name":"borndate", "field":"born_date", "field_type":"date"}
  23.           ],
  24.         "has_many":[{
  25.           "name": "Phones",
  26.           "class_name":"TPhone",
  27.           "child_field_name":"guid_person",
  28.           "lazy_load": false
  29.         }],
  30.         "has_one": {
  31.           "name": "car",
  32.           "class_name":"TCar",
  33.           "child_field_name":"guid_person"
  34.         }
  35.       },
  36.       "TPhone":
  37.       {
  38.         "table": "phones",
  39.         "id": {"name":"guid", "field":"guid", "field_type":"string", "size": 100, "default_value": ""},
  40.         "fields":[
  41.           {"name":"number", "field":"number", "field_type":"string", "size": 100},
  42.           {"name":"kind", "field":"kind", "field_type":"string", "size": 100},
  43.           {"name":"guid_person", "field":"guid_person", "field_type":"string", "size": 100}
  44.           ]
  45.       }
  46.     }
  47. }

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?

25 Responses to “Sneak preview about DORM, The Delphi ORM”

  1. André Says:

    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!

  2. Gad D Lord Says:

    I am interested. Is it open source with SVN?

  3. Daniele Teti Says:

    @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!
    A: Yes, is under consideration. Could be something like the following
    “P:= Session.Load(3);”

    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.

  4. Daniele Teti Says:

    @Gad D Lord:
    Still there arent a public repository. ASAP I’ll setup a googlecode project for DORM

  5. André Says:

    - 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!

  6. Daniele Teti Says:

    @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

  7. Luca Guzzon Says:

    Daniele,
    the DORM code is only Delphi 2010 compliant … or not?.

    luca
    D7 lover

  8. misiak Says:

    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…

  9. Daniele Teti Says:

    @Luca
    DORM is D2010+ compatible. The new RTTI introduced in D2010 is a necessary feature for DORM.

  10. Magno Machado Paulo Says:

    Looks promising!

    One question: Is lazy-load restricted to TdormCollection or any association can be lazy-load?

  11. Daniele Teti Says:

    @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.

  12. Daniele Teti Says:

    @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.

  13. Moritz Beutel Says:

    DORM is a wonderful name for a persistence framework, it even beats Hibernate :)

  14. Andrew Tierney Says:

    Do you have the code available on CodePlex ? SourceForge or GitHub, etc etc ?

    Keen to give the beast a run..!

  15. justin Says:

    Looks very promising. One question though: why don’t you use an xml file configuration for dorm.conf? Why re invent the wheel?

  16. Daniele Teti Says:

    @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

  17. Jeroen Pluimers Says:

    How does this compare to http://sourceforge.net/projects/larryhengensopf/ ?
    –jeroen

  18. Daniele Teti Says:

    @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.

  19. mrbar2000 Says:

    Can u post a link to the beta code of DORM?

  20. Claudio Piffer Says:

    Hi Daniele,

    really a very interesting work.

    Thank you very much

  21. Lucas (Brasil) Says:

    How did you solve the LazyLoading problem, if Delphi does not have the facility of dynamic proxy?

  22. Daniele Teti Says:

    @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.

  23. Lucas (Brasil) Says:

    What a way to implement the design pattern LazyLoad did you use?

    Virtual proxy, Ghost, Value holder?

    Many thanks.

  24. while true do; » Blog Archive » dorm, “The Delphi ORM”, officially published at ITDevCon Says:

    [...] begins as a my personal project more than one year ago, and now is sponsored by bitTime Software that offers custom development, consultancy, mentoring [...]

  25. asavasamuel Says:

    Here is an ORM that works with Firebird
    https://www.kellermansoftware.com/p-47-net-data-access-layer.aspx

Leave a Reply

You must be logged in to post a comment.

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in