Jump to content
3ddark

How to access RTTI property(class type) and play on

Recommended Posts

  [Entity('addresses', 'public')]
  TAddress = class(TEntity)
  private
    FCity: string;
    FCountry: string;
  public
    [Column('city')]
    property City: string read FCity write FCity;
    [Column('country')]
    property Country: string read FCountry write FCountry;
  end;

  [Entity('persons', 'public')]
  TPerson = class(TEntity)
  private
    FName: string;
    FSurName: string;
    FAddress: TAddress;

    function GetFullName: string;
  public
    [Column('name')]
    property Name: string read FName write FName;
    [Column('sur_name')]
    property SurName: string read FSurName write FSurName;
    [NotMapped]
    property FullName: string read GetFullName;

    [OneToOne('')]
    property Address: TAddress read FAddress write FAddress;	//Access this property and modify (like City='Istanbul', Country='Turkey')
  end;

////////////////////////////////

function TEntity.GetQuery(AID: Int64; AWithSchema: Boolean): string;
var
  ACtx: TRttiContext;
  AType: TRttiType;
  AProp : TRttiProperty;
  LAttr : TCustomAttribute;

  LColName : string;
  LTableName : string;
  LColNames: string;
  LColID: string;
begin
  ACtx := TRttiContext.Create;
  try
    AType := ACtx.GetType(SomeClass.ClassType);
    LTableName := Self.GetTableName(AWithSchema);

    LColNames := '';
    for AProp in AType.GetProperties do
    begin
      if AProp.Visibility in [mvPublished, mvPublic] then
      begin
        for LAttr in AProp.GetAttributes do
        begin
          if LAttr is Column then
          begin
            LColName := Column(LAttr).ColumnName;
            if LColName = '' then
              raise Exception.Create('Column must bu declared.' + sLineBreak + 'If it not used, declare "NotMapped" attribute!!!');
            if LColName = 'id' then
              LColID := LColName
            else
              LColNames := LColNames + LColName + ',';
          end;
        end;
      end;
    end;

    if LColNames = '' then
      raise Exception.Create('SQL field_names not found!!!');

    LColNames := LColID + ',' + LColNames;

    Result := Trim('SELECT ' + LeftStr(LColNames, Length(LColNames)-1) + ' FROM ' + LTableName + ' WHERE ' + LeftStr(LColNames, 2) + '=' + IntToStr(AID));
  finally
    ACtx.Free;
  end;
end;

///////////////////////////////////////////
procedure Test
var
  ACtx: TRttiContext;
  AType: TRttiType;
  AProp, AProp2: TRttiProperty;
  LAttr: TCustomAttribute;
  LInst: TRttiInstanceType;
  ARelations: TArray<TRttiProperty>;
  LColName: string;
  LTableName: string;
  LColNames: string;
  LColID: string;
  LClass: TClass;
  LPointer: Pointer;
begin
  ACtx := TRttiContext.Create;
  try
    AType := ACtx.GetType(SomeModel.ClassType);
    SetLength(ARelations, 0);
    for AProp in AType.GetProperties do
    begin
      if (AProp.PropertyType.TypeKind = tkClass) and (AProp.IsReadable) and (AProp.IsWritable) and (AProp.Visibility in [mvPublished, mvPublic]) then
      begin
        for LAttr in AProp.GetAttributes do
        begin
          //access here class property and fill an array
          if (LAttr is OneToOne) or (LAttr is OneToMany) then
          begin
            SetLength(ARelations, Length(ARelations) + 1);
            AReletions[Length(ARelations) - 1] := AProp;
            Break;
          end;
        end;
      end;
    end;

    for AProp in ARelations do
    begin
      //HEREEEEE I WANT TO ACCESS model and update property values
      LInst := AProp.PropertyType.AsInstance;
      Move(LInst, LPointer, SizeOf(Pointer));

      AType := ACtx.GetType(LInst.Handle);
      for AProp2 in AType.GetProperties do
      begin
        if (AProp2.Visibility in [mvPublished, mvPublic]) then
        begin
          for LAttr in AProp2.GetAttributes do
          begin
            if (LAttr is ColumnAttribute) then
            begin
              if (AProp2.PropertyType.TypeKind = tkString) or (AProp2.PropertyType.TypeKind = tkUString) or (AProp2.PropertyType.TypeKind = tkWString) then
              begin
                AProp2.SetValue(LPointer, 'Orange');
                Break;
              end;
            end;
          end;
        end;
      end;
      LClass := AProp.PropertyType.AsInstance.MetaclassType;
    end;
  finally
    ACtx.free;
  end;
end;

I want to access  Address (FAddress) property and edited city and/or country values.

 

I can do anything in simple class with RTTI
I can do all SELECT, INSERT, UPDATE, DELETE operations.

But in order to be able to perform operations in classes with Relations, I must be able to perform the necessary operations to access these classes.

 

I'll add the Github link after I've made the final code edits.

I may have shown the exact code I wrote and the missing part.

 

NOTE: The codes may not work fully, I added it as a representation.

Edited by 3ddark

Share this post


Link to post

You are not actually reading the TPerson.Address property value to access the TAddress object.  AProp.PropertyType.AsInstance is not the way to do that, use AProp.GetValue() instead and cast the result to an object pointer, eg:

var
  ...
  LObj: TObject;
...
for AProp in ARelations do
begin
  LObj := AProp.GetValue(SomeModel).AsObject;
  LClass := LObj.ClassType;

  for AProp2 in AProp.PropertyType.GetProperties do
  begin
    if (AProp2.Visibility in [mvPublished, mvPublic]) then
    begin
      for LAttr in AProp2.GetAttributes do
      begin
        if (LAttr is ColumnAttribute) then
        begin
          if (AProp2.PropertyType.TypeKind in [tkString, tkUString, tkWString]) then
            AProp2.SetValue(LObj, 'Orange');
          Break;
        end;
      end;
    end;
  end;
end;
...

 

Edited by Remy Lebeau

Share this post


Link to post
10 hours ago, Remy Lebeau said:

You are not actually reading the TPerson.Address property value to access the TAddress object.  AProp.PropertyType.AsInstance is not the way to do that, use AProp.GetValue() instead and cast the result to an object pointer, eg:


var
  ...
  LObj: TObject;
...
for AProp in ARelations do
begin
  LObj := AProp.GetValue(SomeModel).AsObject;
  LClass := LObj.ClassType;

  for AProp2 in AProp.PropertyType.GetProperties do
  begin
    if (AProp2.Visibility in [mvPublished, mvPublic]) then
    begin
      for LAttr in AProp2.GetAttributes do
      begin
        if (LAttr is ColumnAttribute) then
        begin
          if (AProp2.PropertyType.TypeKind in [tkString, tkUString, tkWString]) then
            AProp2.SetValue(LObj, 'Orange');
          Break;
        end;
      end;
    end;
  end;
end;
...

 

 

I was able to make the change exactly as you said in the tests I did last night.

 

Can I convert AProp2 to T?

Why do I need this?

I have a function like GetByID(AID: Int64; ALock: Boolean = False)

I want to Recursive invoke properties that come as tkClass and contain attributes "OneOne" or "OneMany".

 

(*
	Person Table
	id		name		sur_name	address_id
	1		3ddark		Thunder		2

	Address Table
	id		city		country
	1		Ankara		Turkey
	2		Istanbul	Turkey
*)
var
  LMan: TEntityManager;
  LPerson: TPerson;
begin
  LMan := TEntityManager.Create(DBConnection);
  LPerson := LMan.GetById<TPerson>(1, False);		//Here once call and see tkClass recursive call Address, or any class relations
  
  Writeln(LPerson.Id.ToString());			//1
  Writeln(LPerson.Name);					//3ddark
  Writeln(LPerson.SurName);					//Thunder
  Writeln(LPerson.Address.Id.ToString());	//2
  Writeln(LPerson.Address.City);			//Istanbul
  Writeln(LPerson.Address.Country);			//Turkey
end;






function TEntityManager.GetById<T>(AID: Int64, ALock: Boolean = False): T;
var
  LRelationID: Int64;
  ...
  LObj: TObject;
...
begin
  ...
  for AProp in ARelations do
  begin
    LObj := AProp.GetValue(SomeModel).AsObject;
    LClass := LObj.ClassType;
    //Normally TPerson when T comes first
    //But I need TAddress or any relations TXXX class as T
    AProp.SetValue(LObj, Self.GetById<(*Here dynamic classType like TAddress or TXxxx --->*)LObj.ClassType>(LRelationID, ALock)).AsObject;
  end;
  ...
end;

I hope I was able to explain my problem.

Thanks in advance.

Edited by 3ddark
code editing

Share this post


Link to post
9 hours ago, 3ddark said:

Can I convert AProp2 to T?

No.  AProp2 is just a TRttiProperty, you can't cast it to anything that is related to the actual object.

9 hours ago, 3ddark said:

I have a function like GetByID(AID: Int64; ALock: Boolean = False)

I want to Recursive invoke properties that come as tkClass and contain attributes "OneOne" or "OneMany".

You can't specify Generic parameters at runtime, only at compile-time.  So you will have to change GetById() into a normal function that takes a TClass input parameter, eg:

function TEntityManager.GetById(ClassType: TClass; AID: Int64, ALock: Boolean = False): TObject;
...
AProp.SetValue(LObj, Self.GetById(LObj.ClassType, LRelationID, ALock));

 

Share this post


Link to post

Thanks for your support @Remy Lebeau

Here is the Github Repo.

 

Use Zeos DB Connection and use PostgreSQL database

 

Simple class GetByID => Done

Simple class with OneToOne => Done

Simple class with OneToMany => waiting. I use to TArray<TAnyEntityClass> tkDynArray fill all data

 

After the GetById operation is done. I will add other features and check all

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×