#1 2021-04-06 08:45:43

tbo
Member
Registered: 2015-04-20
Posts: 349

mORMot2: TOrm derived from TSynPersistent

I have a simple question, but it is very important to me. Does TOrm have to be derived from TSynPersistent? Or could it also be this inheritance order:

{$M+} TSynCustomPersistent = class(TObject) {$M-}
TOrm = class(TSynCustomPersistent)
TSynPersistent = class(TSynCustomPersistent)

Descendants of TSynCustomPersistent can be used with interfaces. With TSynPersistent comes the break.

The background of my question is that I use direct data binding for local data objects. Since TSynPersistent does not work with interfaces, I can no longer use the following:

TSQLBaseRecord = class abstract(TSQLRecord, IInterface, INotifyPropertyChangedEx)
...

TSQLArticle = class(TSQLBaseRecord)
...
published
  property Name: RawUtf8
    read FName write SetName;
end;

In the form, binding between input elements and data object is then done with a simple statement.

edtName.AddBinding(SourceObject, TSQLArticle.OPN.Name, BindingGroup);

This approach is very practical and a lot of existing controls are prepared for it.

With best regards
Thomas

Last edited by tbo (2021-04-06 08:50:05)

Offline

#2 2021-04-06 09:58:31

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,548
Website

Re: mORMot2: TOrm derived from TSynPersistent

I don't understand your use case of interfaces with such a non TInterfacedObject type.
It is unsafe and never a good idea to mix the memory models.
If TOrm is an interfaced class, then all existing code requiring Create + Free would break with _AddRef/_Release memory management.

What is your exact use case?

Offline

#3 2021-04-06 12:13:59

tbo
Member
Registered: 2015-04-20
Posts: 349

Re: mORMot2: TOrm derived from TSynPersistent

ab wrote:

What is your exact use case?

I use Spring4D and DSharp by Stefan Glienke. The binding between input element and data object is done with the bindings classes from DSharp. To make it a bit more understandable I have to write some source code. I hope it is still ok.

I put together a framework for myself that combines various free and commercial component collections (Spring4D, DSharp, DevExpress, etc.). The goal is to connect form and data object easily, quickly and yet still flexibly.

To make an object bindable with DSharp, I need to write the following:

TSQLBaseRecord = class abstract(TSQLRecord, IInterface, INotifyPropertyChangedEx)
private
  FPropertyChanged: Event<TPropertyChangedEvent>;
  function GetOnPropertyChanged: IEvent<TPropertyChangedEvent>;
  function QueryInterface(const pmcIID: TGUID; out pmoObject): HResult; stdcall;
  function _AddRef: Integer; stdcall;
  function _Release: Integer; stdcall;
protected
  procedure NotifyOfPropertyChange(const pmcPropertyName: String; pmUpdateTrigger: TUpdateTrigger = utPropertyChanged);
end;

Definition of the data object:

TSQLArticle = class(TSQLBaseRecord)
strict private
  type
    TOPNRec = record
      const Name = 'Name';
    end;
private
  FName: RawUtf8;
  procedure SetName(const pmcValue: RawUtf8);
public
  const OPN: TOPNRec = ();
published
  property Name: RawUtf8
    read FName write SetName;
end;

procedure TSQLArticle.SetName(const pmcValue: RawUtf8);
begin
  FName := pmValue;
  NotifyOfPropertyChange(OPN.Name);
end;

The creation of the edit form:

TfrmArticle = class(TBaseOrmRecordEditForm)
  edtName: TcxTextEdit;
protected
  procedure InitDataBindings; override;
  procedure InitFormLayout(const pmcLayoutBuilder: ILayoutBuilder); override;
end;

procedure TfrmArticle.InitDataBindings;
begin
  edtName.AddBinding(SourceObject, TSQLArticle.OPN.Name, BindingGroup)
    .ValidationRules.Add(TEditMustNotEmptyValRule.Create(TSQLArticle.OPN.Name));
end;

procedure TfrmArticle.InitFormLayout(const pmcLayoutBuilder: ILayoutBuilder);
begin
  with pmcLayoutBuilder do
  begin
    NewGroup;
      CreateItem(edtName, 'Name:').CaptionWidth(80).ControlWidth(250);
    __EndGroup;
  end;

  inherited InitFormLayout(pmcLayoutBuilder);
end;

That's all you need to do in a form for data binding and layout. My view is the view of an simple application developer. With the help of this framework, the object inspector and the setting of many properties has largely become obsolete. I hope I could make my intention understandable.

With best regards
Thomas

Offline

#4 2021-04-06 17:41:59

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,548
Website

Re: mORMot2: TOrm derived from TSynPersistent

Now it makes perfect sense!

I have just introduced the new TObjectWithCustomCreate which is used e.g. as TOrm/TSynPersistent parent class - as a side effect, it will allow TOrm to implement an interface when needed.

Offline

#5 2021-04-07 16:17:19

tbo
Member
Registered: 2015-04-20
Posts: 349

Re: mORMot2: TOrm derived from TSynPersistent

Arnaud, thank you very much for the changes.

My very first still cautiously presented assessment of mORMot2 is: I would like to congratulate you for the excellent work on mORMot2. mORMot1 was fast. But the numbers for mORMot2 are so much better that I want to wait for real operation first, because otherwise I almost can't believe it. It feels noticeably faster. I am very happy with what I see.

I would like to say something more detailed about the background of my change request. I don't know if this is the best solution, but for me it works without problems.

My program uses a local database, but most of the data comes from the server. I describe here what is going on in the client program. To simplify, the data objects (descendant of class TOrm) for the local data gets the binding directly. The data objects representing the data from the server have a lightweight ancestor (descendant of TBom see details later). I achieve this with the following methods.

Data objects are defined with all published properties in a common unit as custom objects.

TOrmCustomArticle = class(TOrmCustomRecord)
...
published
  property Name: RawUtf8
    read FName write SetName;
end;

The custom object is concretized in a unit for the server and the client.

u_ServerModel.pas
TOrmArticle = class(TOrmCustomArticle)
end;

u_ClientModel.pas
TOrmArticle = class(TOrmCustomArticle)
end;

Different definitions can be used to change the inheritance order. Here is an extract from the BOMCustomRecordH.inc file:

{$IFDEF BOWithBinding}
  {$IFDEF BOWithBindingAndORM}
    TOrmBaseRecord = class abstract(TOrm, IInterface, INotifyPropertyChangedEx)
  {$ELSE}
    TBom = class(TObjectWithCustomCreate)
    private
      FID: TID;
    public
      constructor Create; override;
      property IDValue: TID
        read FID write FID;
    published
      property ID: TID
        read FID;
    end;

    TOrmBaseRecord = class abstract(TBom, IInterface, INotifyPropertyChangedEx)
  {$ENDIF}
    private
      FPropertyChanged: Event<TPropertyChangedEvent>;
      ...
    protected
      procedure NotifyOfPropertyChange(const pmcPropertyName: String; pmUpdateTrigger: TUpdateTrigger = utPropertyChanged);
    end;  
{$ELSE}  // ORM pure custom record
  TOrmBaseRecord = class abstract(TOrm);
{$ENDIF}

The inheritance structure looks like this:

Data objects representing the local data:
TOrmArticle -> TOrmCustomArticle -> TOrmCustomRecord -> TOrmBaseRecord -> TOrm -> TObjectWithCustomCreate -> TObject

Data objects representing the data from server:
TOrmArticle -> TOrmCustomArticle -> TOrmCustomRecord -> TOrmBaseRecord -> TBom -> TObjectWithCustomCreate -> TObject

Buisiness objects derived from a lightweight class can be equipped with the necessary functions for convenient work in the client. They can be loaded from server via interface based services as follows:

IArticle = interface(IInvokable)
['{2FF6B866-FEE0-454E-A575-29702269285A}']
  procedure GetAllItems(out pmoList: TOrmArticleObjArray);
end;

var
  service: IArticle;
  dataArr: TOrmArticleObjArray;
begin
  if dmDB.ServerRestHttp.Resolve(IArticle, service) then
  begin
    service.GetAllItems(dataArr);
    FListView.Presenter.View.ItemsSource := TOrmArticleList.Create(dataArr) as IObjectList;
  end;
end;

I hope my explanations were understandable and perhaps helpful for others. And hopefully not too lengthy.

With best regards
Thomas

Offline

Board footer

Powered by FluxBB