3ddark 3 Posted June 21, 2023 (edited) [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 June 21, 2023 by 3ddark Share this post Link to post
Remy Lebeau 1398 Posted June 21, 2023 (edited) 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 June 21, 2023 by Remy Lebeau Share this post Link to post
3ddark 3 Posted June 22, 2023 (edited) 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 June 22, 2023 by 3ddark code editing Share this post Link to post
Remy Lebeau 1398 Posted June 22, 2023 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
3ddark 3 Posted June 22, 2023 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