Getting an object and all it's descendants

Hi Wagner,


In the Music Library example the function FManager.FindAll<TMediaFile> returns all TMediaFile objects and the descendant objects TSong and TVideo.

SELECT ....
FROM MEDIA_FILES A
  LEFT JOIN VIDEO_FORMATS B ON (B.ID = A.ID_VIDEO_FORMAT)
  LEFT JOIN SONG_FORMATS C ON (C.ID = A.ID_SONG_FORMAT)
WHERE A.MEDIA_TYPE IN (:p_0, :p_1, :p_2)

p_0 = NULL (ftString)
p_1 = "VIDEO" (ftString)
p_2 = "SONG" (ftString)

My question is how does Aurelius know to include the descendant objects?  I've been trying to do something similar but I'm getting this: 

SELECT ....
FROM LOCATIONS A
WHERE A.LOCATION_TYPE = :p_0

p_0 = NULL (ftString) 

i.e. it doesn't know about it's children

My base class is
  [Entity]
  [Table('LOCATIONS')]
  [Sequence('GEN_LOCATIONS_ID')]
  [Inheritance(TInheritanceStrategy.SingleTable)]
  [DiscriminatorColumn('LOCATION_TYPE', TDiscriminatorType.dtString)]
  [Id('FID', TIdGenerator.IdentityOrSequence)]
  TLocation = class(TPersistent)

and in separate units I have

  [Entity]
  [DiscriminatorValue('DEPOT')]
  TDepot = class(TLocation)

  [Entity]
  [DiscriminatorValue('STORE')]
  TStore = class(TLocation)

  [Entity]
  [DiscriminatorValue('HATCH')]
  THatch = class(TStore)

Any ideas?


I'm not sure if I understand what you are trying to ask.

But in the case of media library, properties of inherited objects are being saved in a different table (TInheritanceStrategy.JoinedTables). In your example, you are using TInheritanceStrategy.SingleTable, so objects of all classes are saved in a single table and Aurelius differentiate them by the LOCATION_TYPE field. There is nothing wrong with what you wrote there?

My problem is that FManager.FindAll<TLocation> only looks for ones where the DiscriminatorColumn is null.  It doesn't add onto the where clause the other values like it does in the example.  


The SQL is just :

FROM LOCATIONS A
WHERE A.LOCATION_TYPE = :p_0

not:

FROM LOCATIONS A
WHERE A.LOCATION_TYPE in (:p_0, :p_1,:p_2,:p_3)

How does it know to add the values from the DiscriminatorColumn in the other objects?

It looks like a typical problem of linker removing classes. Have you used the inherited classes somewhere in your application? If not, Delphi linker will remove then and Aurelius will think you only have the base class.

Your answer sounded like the answer but sadly it isn't.  I've put it in a unit test and tried using the other objects doing the following but still not getting anywhere


var
  Locations: TList<TLocation>;
  l: TLocation;
  h: thatch;
  s: tstore;
  d: tdepot;
begin

  Locations := FManager.FindAll<TLocation>;
  for l in Locations do
  begin
    if l is thatch then
        h := thatch(l)
    else if l is tstore then
        s := tstore(l)
    else if l is tdepot then
        d := tdepot(l);
    if assigned(d) then
        CodeSite.Send(d.name);
  end;
  Locations.Free;

getting this

SELECT A.ID AS A_ID, A.ERP_CODE AS A_ERP_CODE, A.NAME AS A_NAME, A.CREATED_BY AS A_CREATED_BY, A.CREATED_DATE AS A_CREATED_DATE, A.UPDATED_DATE AS A_UPDATED_DATE, A.UPDATED_TIME AS A_UPDATED_TIME, A.ENABLED AS A_ENABLED, A.UPDATED_BY AS A_UPDATED_BY, A.SHORT_NAME AS A_SHORT_NAME, A.LOCATION_TYPE AS A_LOCATION_TYPE, A.SITE_ID AS A_SITE_ID, A.ACCOUNT_ID AS A_ACCOUNT_ID
FROM LOCATIONS A
WHERE A.LOCATION_TYPE = :p_0

p_0 = NULL (ftString)

I don't see any problem at all. Must be something else. Have you tried to add a discriminator value to root class just in case?

I've come back to this this afternoon and looks like you were right but I'm surprised that the comparison of the object to the class isn't good enough. I think this could catch others out too so one to watch out for. 

  Locations := FManager.FindAll<TLocation>;
  for l in Locations do
  begin
    if l is thatch then
        h := thatch(l)
    else if l is tstore then
        s := tstore(l)
    else if l is tdepot then
        d := tdepot(l);
    if assigned(d) then
        CodeSite.Send( csmLevel1, d.name );
  end;
  Locations.Free;

  Depots := FManager.FindAll<TDepot>;
  for d in Depots do
        CodeSite.Send( csmLevel2, d.name );
  depots.Free;

//  Hatches := FManager.FindAll<THatch>;
//  for h in Hatches do
//        CodeSite.Send( csmLevel2, h.name );
//  Hatches.Free;

gives
FROM LOCATIONS A
WHERE A.LOCATION_TYPE IN (:p_0, :p_1)

p_0 = NULL (ftString)
p_1 = "DEPOT" (ftString)

uncommenting the Hatches gives everything

FROM LOCATIONS A
WHERE A.LOCATION_TYPE IN (:p_0, :p_1, :p_2, :p_3)

p_0 = NULL (ftString)
p_1 = "STORE" (ftString)
p_2 = "DEPOT" (ftString)
p_3 = "HATCH" (ftString)

It makes it a little more awkward to use this way.  

Well, in a real environment you would use the classes anyway. Thanks for the feedback.Wagner Landgraf2014-10-08 11:14:37

I put all the TLocation objects in one unit and have added an initialisation section to get round this as follows:
initialization
  THatch.Create.Free;
  TStore.Create.Free;
  TDepot.Create.Free;

otherwise I was getting this error when loading instantiating a class that used a descendant of TLocation 

"Proxy loading failed. Results did not return a single object. (C:\Data\Documents\TMSSoftware\TMS Aurelius\source\core\Aurelius.Engine.ObjectManager.pas, line 1581)"