Persistent derived attributes

A small example of making derived attributes which are persisted to the database.

Persistent derived attributes in Bold

Bold's derived attributes are always evaluated in memory, this makes it impossible to perform an InPS (persistence storage) search against certain data. Any search expression which refers to a derived attribute will cause Bold to load all objects from the database into memory first for an in-memory evalution, this can be very slow and resource consuming.



Past discussions have been posted on the support groups asking how to achieve derived attributes which are also persisted to the DB for SQL evaluation. Today I had the same requirement, and this is the solution I came up with.



The first thing to do is to avoid marking the attribute as derived in the model. Instead, make the attribute a persistent attribute, eg



  FullName: string (128)



The next step is to ensure that the value of FullName is always up to date, so we need to override ReceiveEventFromOwned in each descendant class we create.




procedure TPerson.ReceiveEventFromOwned((Originator: TObject; OriginalEvent: TBoldEvent);

begin

  inherited;

  if (OriginalEvent = beCompleteModify) and (Originator = M_Name) then

    FullName := M_FirstName.AsString + ' ' + M_LastName.AsString;

end;






This will ensure that the attribute is always up to date but the weakness here, which we will now address, is that it is possible to alter FullName manually. To solve this problem we need to make FullName read only, like so




function TPerson.ReceiveQueryFromOwned(Originator: TObject; OriginalEvent: TBoldEvent; const Args: array of const; Subscriber: TBoldSubscriber): Boolean;

begin

  Result := inherited ReceiveQueryFromOwned(Originator, OriginalEvent, Args, Subscriber);

  if not Result or BoldObjectIsDeleted then Exit;

  if (Originator = M_FullName) and (OriginalEvent = bqMayModify) then

  begin

    Result := False;

    //Unexpected error

    SetBoldLastFailureReason(TBoldFailureReason.Create('FullName is immutable', Self));

  end;

end;






FullName can now no longer be modified, unfortunately the error we have introduced is that we can no longer set the value of FullName in our derivation code.



To solve this final problem we need a way of identifying user/normal-code changes and "internal" changes. To do this I always implement a common pattern to my base object, like so

1) Create a Delphi attribute - InternalChangeCount: Integer

2) Add a protected method BeginInternalChange

3) Add a protected method EndInternalChange

4) Add a protected method IsInternalChange: Boolean




//Note: TRootObject is the root object of my model

procedure TRootObject.BeginInternalChange;

begin

  Inc(fInternalChangeCount);

end;



procedure TRootObject.EndInternalChange;

begin

  if fInternalChangeCount = 0 then

    raise Exception.Create('EndInternalChange without BeginInternalChange');

  Dec(fInternalChangeCount);

end;



function TRootObject.IsInternalChange: Boolean;

begin

  Result := (InternalChangeCount > 0);

end;






Finally, we just need to update the original source code so that

1) Setting the derived code is done within an "internal change"

2) Modifications to FullName are permitted if the change is an internal one.




procedure TPerson.ReceiveEventFromOwned(Originator: TObject; OriginalEvent: TBoldEvent);

begin

  inherited;

  if OriginalEvent = beCompleteModify then

  begin

    if (Originator = M_FirstName) or (Originator = M_LastName) then

    try

      BeginInternalChange;

      FullName := M_FirstName.AsString + ' ' + M_LastName.AsString;

    finally

      EndInternalChange;

    end;

  end;

end;



function TPerson.ReceiveQueryFromOwned(Originator: TObject; OriginalEvent: TBoldEvent; const Args: array of const; Subscriber: TBoldSubscriber): Boolean;

begin

  Result := inherited ReceiveQueryFromOwned(Originator, OriginalEvent, Args, Subscriber);

  if not Result or BoldObjectIsDeleted then Exit;

  if (Originator = M_FullName) and (OriginalEvent = bqMayModify) then

  begin

    if IsInternalChange then

      Result := True

    else

      Result := False;

    //Unexpected error

    if not Result then

      SetBoldLastFailureReason(TBoldFailureReason.Create('FullName is immutable', Self));

  end;

end;






In the application I am creating the object structure is actually as follows:

  RootObject (ModelRoot)

    RoledObject (Has a FullName)

      Person (FullName = FirstName + ' ' + LastName)

      Department (FullName = Name)

      Company (FullName = Name)



Conclusion:

This technique requires only a small piece of code for every descendant class (RecevieEventFromOwned) whilst providing the ability to perform searches on the DB server against a derived attribute. I feel that the benefits greatly outweigh the small amount of work required to achieve them.

 

Share this article!

Follow us!

Find more helpful articles: