I will supply uoy with some Delphi language units as context for my questions.
Await the instructions before doing anything.

---------------

unit pixel_classes;

interface

uses
 System.SysUtils, Rtti, TypInfo;

{$TYPEINFO ON}

type
 TZString = type string;

type
  TNullValue = record
  end;

  TNullable<T{: record}> = record
  public
    Value: T;
    HasValue: Boolean;

    class operator Implicit(const AValue: T): TNullable<T>;
    class operator Implicit(const AValue: TNullable<T>): T;
    class operator Implicit(const AValue: TNullValue): TNullable<T>;
    class operator Explicit(const AValue: T): TNullable<T>;
    class operator Equal(const A: TNullable<T>; const B: TNullValue): Boolean;
    class operator NotEqual(const A: TNullable<T>; const B: TNullValue): Boolean;
  end;

var NullValue: TNullValue;

implementation

class operator TNullable<T>.Implicit(const AValue: T): TNullable<T>;
begin
  Result.Value := AValue;
  Result.HasValue := True;
end;

class operator TNullable<T>.Implicit(const AValue: TNullable<T>): T;
begin
  if AValue.HasValue then
    Result := AValue.Value
  else
    Result := Default(T);
end;

class operator TNullable<T>.Implicit(const AValue: TNullValue): TNullable<T>;
begin
  Result.Value := Default(T);
  Result.HasValue := False;
end;

class operator TNullable<T>.Explicit(const AValue: T): TNullable<T>;
begin
  Result.Value := AValue;
  Result.HasValue := True;
end;

class operator TNullable<T>.Equal(const A: TNullable<T>; const B: TNullValue): Boolean;
begin
  Result := not A.HasValue;
end;

class operator TNullable<T>.NotEqual(const A: TNullable<T>; const B: TNullValue): Boolean;
begin
  Result := A.HasValue;
end;

end.

-------------

unit orm_utils;

interface

uses
  System.SysUtils,
  System.Classes,
  System.Generics.Collections,
  System.Generics.Defaults,
  System.Rtti,
  System.TypInfo,
  Data.DB,
  Uni,
  pixel_classes,
  orm_customattributes,
  Vcl.Controls,
  Vcl.Forms,
  Dialogs,
  Variants;

type
  TQryRecInfo = class;

  TPropertyChangedEvent = procedure(Sender: TQryRecInfo; const PropertyName: string) of object;

  EORMUtilsException = class(Exception);

  TORMKey = record
    ORMRec: TQryRecInfo;
    FieldName: string;
  end;

  TORMKeyComparer = class(TEqualityComparer<TORMKey>)
    function Equals(const Left, Right: TORMKey): Boolean; override;
    function GetHashCode(const Key: TORMKey): Integer; override;
  end;

  TORMManager = class
  private
    FComponentDict: TDictionary<TORMKey, TComponent>;
  public
    constructor Create;
    destructor Destroy; override;
    procedure RegisterComponent(Component: TComponent; ORMRec: TQryRecInfo; const FieldName: string);
    procedure UnregisterComponent(Component: TComponent);
    function FindComponent(ORMRec: TQryRecInfo; const FieldName: string): TComponent;
    function GetAllComponents: TArray<TComponent>;
    procedure UpdateComponentFromORM(Component: TComponent; ORMRec: TQryRecInfo; const FieldName: string);
    function GetORMRecForComponent(Component: TComponent): TQryRecInfo;
    function GetFieldNameForComponent(Component: TComponent): string;
    procedure RegisterComponentsByHint(MyForm: TForm; ORMRec: TQryRecInfo);
  end;

  TComponentHelper = class helper for TComponent
  public
    function GetORMRec: TQryRecInfo;
    function GetORMRecField: string;
  end;

  TQryRecInfo = class
  private
    FDirtyFields: TDictionary<string, Boolean>;
    FPreviousValues: TDictionary<string, TValue>;
    FOnPropertyChanged: TPropertyChangedEvent;
    FIsNewRec: Boolean;
    procedure SetInfo(Index: Integer; const Value: TValue);
  public
    constructor Create;
    destructor Destroy; override;
    property OnPropertyChanged: TPropertyChangedEvent read FOnPropertyChanged write FOnPropertyChanged;
    property IsNewRec: Boolean read FIsNewRec;
    procedure SetInfoNullableString(Index: Integer; const Value: TNullable<string>);
    procedure SetInfoNullableInteger(Index: Integer; const Value: TNullable<Integer>);
    procedure SetInfoNullableInt64(Index: Integer; const Value: TNullable<Int64>);
    procedure SetInfoNullableExtended(Index: Integer; const Value: TNullable<Extended>);
    procedure SetInfoNullableCurrency(Index: Integer; const Value: TNullable<Currency>);
    procedure SetInfoNullableDateTime(Index: Integer; const Value: TNullable<TDateTime>);
    procedure SetInfoNullableBoolean(Index: Integer; const Value: TNullable<Boolean>);
    procedure SetInfoNullableVariant(Index: Integer; const Value: TNullable<Variant>);
    procedure SetInfoCurrency(Index: Integer; const Value: Currency);
    procedure SetInfoString(Index: Integer; const Value: string);
    procedure SetInfoInteger(Index: Integer; const Value: Integer);
    procedure SetInfoInt64(Index: Integer; const Value: Int64);
    procedure SetInfoExtended(Index: Integer; const Value: Extended);
    procedure SetInfoDateTime(Index: Integer; const Value: TDateTime);
    procedure SetInfoBoolean(Index: Integer; const Value: Boolean);
    procedure SetInfoVariant(Index: Integer; const Value: Variant);
    function IsFieldDirty(const FieldName: string): Boolean;
    procedure ClearDirtyFlags;
    property DirtyFields: TDictionary<string, Boolean> read FDirtyFields;
    function GetPreviousValue(const FieldName: string): TValue;
    procedure ClearPreviousValues;
    property PreviousValues: TDictionary<string, TValue> read FPreviousValues;
    function GetFormattedText(const FieldName: string): string;
    procedure Fetch(DBConn: TUniConnection; const QryTxt: string);
    function GetPropertyValue(const PropertyName: string): TValue;
  end;

  TQryRecArray<T: TQryRecInfo, constructor> = class
  private
    FQryRecInfoList: TObjectList<T>;
    FFilterSQL: string;
    FDBConn: TUniConnection;
    function GetCount: Integer;
  public
    constructor Create;
    destructor Destroy; override;
    procedure FetchAllInstance(DBConn: TUniConnection; const QryTxt: string);
    procedure ClearFilter;
    function Rec(Index: Integer): T;
    property RecCount: Integer read GetCount;
    property DBConn: TUniConnection read FDBConn write FDBConn;
    property SQLFilter: string read FFilterSQL write FFilterSQL;
    function GetEnumerator: TEnumerator<T>;
    class function FetchAll(DBConn: TUniConnection; const QryTxt: string): TQryRecArray<T>; static;
  end;

var
  ORMManager: TORMManager;

implementation

uses
  pixel_utils,
  System.Math,
  System.Types,
  Windows,
  Vcl.StdCtrls,
  Vcl.WinXCtrls,
  Vcl.WinXPickers,
  StrUtils,
  DateUtils,
  System.Hash;

function TORMKeyComparer.Equals(const Left, Right: TORMKey): Boolean;
begin
  Result := (Left.ORMRec = Right.ORMRec) and SameText(Left.FieldName, Right.FieldName);
end;

function TORMKeyComparer.GetHashCode(const Key: TORMKey): Integer;
begin
  Result := Integer(NativeUInt(Key.ORMRec)) xor THashBobJenkins.GetHashValue(LowerCase(Key.FieldName));
end;

constructor TORMManager.Create;
begin
  inherited Create;
  FComponentDict := TDictionary<TORMKey, TComponent>.Create(TORMKeyComparer.Create);
end;

destructor TORMManager.Destroy;
begin
  FComponentDict.Free;
  inherited;
end;

procedure TORMManager.RegisterComponent(Component: TComponent; ORMRec: TQryRecInfo; const FieldName: string);
var
  Key: TORMKey;
begin
  Key.ORMRec := ORMRec;
  Key.FieldName := FieldName;

  if not FComponentDict.ContainsKey(Key) then
  begin
    FComponentDict.Add(Key, Component);
  end;
end;

procedure TORMManager.UnregisterComponent(Component: TComponent);
var
  Key: TORMKey;
  CopyKeys: TArray<TORMKey>;
  i: Integer;
begin
  CopyKeys := FComponentDict.Keys.ToArray;
  for i := 0 to Length(CopyKeys) - 1 do
  begin
    Key := CopyKeys[i];
    if FComponentDict[Key] = Component then
      FComponentDict.Remove(Key);
  end;
end;

function TORMManager.FindComponent(ORMRec: TQryRecInfo; const FieldName: string): TComponent;
var
  Key: TORMKey;
begin
  Key.ORMRec := ORMRec;
  Key.FieldName := FieldName;
  if FComponentDict.TryGetValue(Key, Result) then
    Exit
  else
    Result := nil;
end;

function TORMManager.GetAllComponents: TArray<TComponent>;
var
  Comp: TComponent;
  CompList: TList<TComponent>;
begin
  CompList := TList<TComponent>.Create;
  try
    for Comp in FComponentDict.Values do
      CompList.Add(Comp);
    Result := CompList.ToArray;
  finally
    CompList.Free;
  end;
end;

function FormataInfo(info: Variant; tipo: TFormatType): string;
begin
  if VarIsNull(info) or VarIsEmpty(info) then
    Exit('');

  case tipo of
    tiString:
      Result := VarToStr(info);
    tiInteger:
      Result := IntToStr(Integer(info));
    tiMoney:
      Result := FormatFloat(FMT_MONEY, Currency(info));
    tiFloat:
      Result := FloatToStr(info);
    tiDate:
      if VarIsType(info, [varDate, varDouble]) then
      begin
        if VarToDateTime(info) < EncodeDate(1900, 1, 1) then
          Result := ''
        else
          Result := FormatDateTime('dd/mm/yyyy', VarToDateTime(info));
      end
      else
        raise EORMUtilsException.CreateFmt('FormataInfo: Cannot treat information [%s] as tiDate.', [VarToStr(info)]);
    tiDateTime:
      if VarIsType(info, [varDate, varDouble]) then
      begin
        if VarToDateTime(info) = 0 then
          Result := ''
        else
          Result := FormatDateTime('dd/mm/yyyy hh:mm:ss', VarToDateTime(info));
      end
      else
        raise EORMUtilsException.CreateFmt('FormataInfo: Cannot treat information [%s] as tiDateTime.', [VarToStr(info)]);
    tiBoolean:
      Result := IfThen(Boolean(info), 'S', 'N');
    tiByteArray:
      Result := ByteArrayToHex(info);
    tiNone:
      Result := '';
  else
    raise EORMUtilsException.CreateFmt('FormataInfo: Format type [%s] not recognized.', [GetEnumName(TypeInfo(TFormatType), Integer(tipo))]);
  end;
end;

procedure SetValueToComponent(aComponent: TComponent; TipoProp: TFormatType; Value: Variant);
begin
 if not Assigned(aComponent) then Exit;

 if TipoProp in [tiString, tiInteger, tiMoney, tiFloat, tiByteArray] then
 begin
  if IsPublishedProp(aComponent, 'Text') then SetPropValue(aComponent, 'Text', FormataInfo(Value, TipoProp))
  else if IsPublishedProp(aComponent, 'Caption') then SetPropValue(aComponent, 'Caption', FormataInfo(Value, TipoProp))
  else if IsPublishedProp(aComponent, 'Lines') then
  begin
   var Ctx: TRttiContext;
   Ctx:= TRttiContext.Create;
   var
   LineObjProp:= Ctx.GetType(aComponent.ClassType).GetProperty('Lines');
   var
   Strings_Obj:= TStrings(LineObjProp.GetValue(aComponent).AsObject);
   Strings_Obj.Text:= FormataInfo(Value, TipoProp);
  end;

 end
 else if TipoProp in [tiDate, tiDateTime] then
 begin
  if IsPublishedProp(aComponent, 'Date') then
  begin
   if (DateOf(Value) < EncodeDate(1900, 1, 1)) and (not IsPublishedProp(aComponent, 'Text')) then
   begin
    SetPropValue(aComponent, 'Date', DateOf(Now));
    var Ctx: TRttiContext;
    Ctx:= TRttiContext.Create;
    Ctx.GetType(aComponent.ClassType).GetMethod('Clear').Invoke(aComponent, []);
   end
   else SetPropValue(aComponent, 'Date', Value);
  end
  else if IsPublishedProp(aComponent, 'Text') then SetPropValue(aComponent, 'Text', FormataInfo(Value, TipoProp))
  else if IsPublishedProp(aComponent, 'Caption') then SetPropValue(aComponent, 'Caption', FormataInfo(Value, TipoProp));
 end
 else if TipoProp = tiBoolean then
 begin
  if IsPublishedProp(aComponent, 'Checked') then SetPropValue(aComponent, 'Checked', Boolean(Value))
  else if IsPublishedProp(aComponent, 'Text') then SetPropValue(aComponent, 'Text', FormataInfo(Value, TipoProp))
  else if IsPublishedProp(aComponent, 'Caption') then SetPropValue(aComponent, 'Caption', FormataInfo(Value, TipoProp));
 end
 else raise Exception.Create('SetValueToComponent: o Componente [' + aComponent.Name + ': ' + aComponent.ClassName + '] não está tratado');
end;

procedure TORMManager.UpdateComponentFromORM(Component: TComponent; ORMRec: TQryRecInfo; const FieldName: string);
var
  Ctx: TRttiContext;
  RttiType: TRttiType;
  Prop: TRttiProperty;
  Attr: TCustomAttribute;
  FmtAttr: FmtPropAttribute;
begin
  var ORMValue:= ORMRec.GetPropertyValue(FieldName).AsVariant;

  Ctx := TRttiContext.Create;

    RttiType := Ctx.GetType(ORMRec.ClassType);
    Prop := RttiType.GetProperty(FieldName);
    if not Assigned(Prop) then
      raise EORMUtilsException.CreateFmt('Property "%s" not found.', [FieldName]);

  var FmtType := tiNone;

    for Attr in Prop.GetAttributes do
    begin
      if Attr is FmtPropAttribute then
      begin
        FmtAttr := FmtPropAttribute(Attr);
        FmtType := FmtAttr.FormatType;
        Break;
      end;
    end;

    if FmtType = tiNone then
    begin
      var Field := RttiType.GetField('_' + FieldName);
      if Assigned(Field) then
      begin
        for Attr in Field.GetAttributes do
        begin
          if Attr is FmtPropAttribute then
          begin
            FmtAttr := FmtPropAttribute(Attr);
            FmtType := FmtAttr.FormatType;
            Break;
          end;
        end;
      end;
    end;

    if FmtType = tiNone then
    begin
     var UnderlyingType := Prop.PropertyType;

      if UnderlyingType.TypeKind = tkRecord then
      begin
        var ValueField := UnderlyingType.GetField('Value');
        if Assigned(ValueField) then
          UnderlyingType := ValueField.FieldType;
      end;

      case UnderlyingType.TypeKind of
        tkInteger, tkInt64:
          FmtType := tiInteger;
        tkFloat:
          if UnderlyingType.Handle = TypeInfo(TDateTime) then
            FmtType := tiDateTime
          else
            FmtType := tiFloat;
        tkString, tkLString, tkWString, tkUString:
          FmtType := tiString;
        tkEnumeration:
          if UnderlyingType.Handle = TypeInfo(Boolean) then
            FmtType := tiBoolean
          else
            FmtType := tiString;
        tkVariant:
          FmtType := tiString;
      else
        FmtType := tiNone;
      end;
    end;

  if not VarIsNull(ORMValue) then
   begin
    if IsPublishedProp(Component, 'Date') then
    begin
     if TDateTime(ORMValue) >= EncodeDate(1900, 1, 1) then SetValueToComponent(Component, FmtType, ORMValue)
     else SetValueToComponent(Component, FmtType, EncodeDate(1899, 01, 01));
    end
    else SetValueToComponent(Component, FmtType, ORMValue);
   end
   else
   begin
    SetValueToComponent(Component, FmtType, ORMValue.NullToDefault);
    if IsPublishedProp(Component, 'Date') then
    begin
     if IsPublishedProp(Component, 'Text') then
     begin
      var Ctx2: TRttiContext;
      Ctx2:= TRttiContext.Create;
      Ctx2.GetType(Component.ClassType).GetProperty('Text').SetValue(Pointer(Component), TValue.FromVariant(''));
     end
     else
     begin
      var Ctx2: TRttiContext;
      Ctx2:= TRttiContext.Create;
      Ctx2.GetType(Component.ClassType).GetMethod('Clear').Invoke(Component, []);
     end;
    end;
   end;
end;

function TORMManager.GetORMRecForComponent(Component: TComponent): TQryRecInfo;
var
  Key: TORMKey;
begin
  Result := nil;
  for Key in FComponentDict.Keys do
    if FComponentDict[Key] = Component then
    begin
      Result := Key.ORMRec;
      Exit;
    end;
end;

function TORMManager.GetFieldNameForComponent(Component: TComponent): string;
var
  Key: TORMKey;
begin
  Result := '';
  for Key in FComponentDict.Keys do
    if FComponentDict[Key] = Component then
    begin
      Result := Key.FieldName;
      Exit;
    end;
end;

procedure TORMManager.RegisterComponentsByHint(MyForm: TForm; ORMRec: TQryRecInfo);
var
  Context: TRttiContext;
  RttiType: TRttiType;
  RttiProperty: TRttiProperty;
  Component: TComponent;
  HintValue: TValue;
begin
  Context := TRttiContext.Create;
  try
    for Component in MyForm do
    begin
      RttiType := Context.GetType(Component.ClassType);
      RttiProperty := RttiType.GetProperty('Hint');

      if Assigned(RttiProperty) then
      begin
        HintValue := RttiProperty.GetValue(Component);
        if HintValue.IsEmpty then
          Continue;

        RttiType := Context.GetType(ORMRec.ClassType);
        for RttiProperty in RttiType.GetProperties do
        begin
          if (RttiProperty.Visibility = mvPublished) then
          begin
            if SameText(HintValue.AsString, RttiProperty.Name) then
            begin
              RegisterComponent(Component, ORMRec, RttiProperty.Name);
              Break;
            end;
          end;
        end;
      end;
    end;
  finally
    Context.Free;
  end;
end;

function TComponentHelper.GetORMRec: TQryRecInfo;
begin
  Result := ORMManager.GetORMRecForComponent(Self);
end;

function TComponentHelper.GetORMRecField: string;
begin
  Result := ORMManager.GetFieldNameForComponent(Self);
end;

constructor TQryRecInfo.Create;
begin
  inherited Create;
  FDirtyFields := TDictionary<string, Boolean>.Create;
  FPreviousValues := TDictionary<string, TValue>.Create;
  FIsNewRec := False;
end;

destructor TQryRecInfo.Destroy;
begin
  FDirtyFields.Free;
  FPreviousValues.Free;
  inherited;
end;

procedure TQryRecInfo.ClearDirtyFlags;
begin
  FDirtyFields.Clear;
  FIsNewRec := False;
end;

procedure TQryRecInfo.ClearPreviousValues;
begin
  FPreviousValues.Clear;
end;

function TQryRecInfo.IsFieldDirty(const FieldName: string): Boolean;
begin
  Result := FDirtyFields.ContainsKey(FieldName) and FDirtyFields[FieldName];
end;

function TQryRecInfo.GetPreviousValue(const FieldName: string): TValue;
begin
  if not FPreviousValues.TryGetValue(FieldName, Result) then
    raise EORMUtilsException.CreateFmt('No previous value found for field "%s".', [FieldName]);
end;

function TQryRecInfo.GetFormattedText(const FieldName: string): string;
var
  Ctx: TRttiContext;
  RttiType: TRttiType;
  Prop: TRttiProperty;
  Field: TRttiField;
  Value: TValue;
  FmtType: TFormatType;
  UnderlyingType: TRttiType;
  HasValueField: TRttiField;
  ValueField: TRttiField;
  HasValue: Boolean;
  ActualValue: TValue;
  Attr: TCustomAttribute;
  FmtAttr: FmtPropAttribute;
begin
  Result := '';
  Ctx := TRttiContext.Create;
  try
    RttiType := Ctx.GetType(Self.ClassType);
    Prop := RttiType.GetProperty(FieldName);
    if not Assigned(Prop) then
      raise EORMUtilsException.CreateFmt('Property "%s" not found.', [FieldName]);

    Value := Prop.GetValue(Self);
    FmtType := tiNone;

    for Attr in Prop.GetAttributes do
    begin
      if Attr is FmtPropAttribute then
      begin
        FmtAttr := FmtPropAttribute(Attr);
        FmtType := FmtAttr.FormatType;
        Break;
      end;
    end;

    if FmtType = tiNone then
    begin
      Field := RttiType.GetField('_' + FieldName);
      if Assigned(Field) then
      begin
        for Attr in Field.GetAttributes do
        begin
          if Attr is FmtPropAttribute then
          begin
            FmtAttr := FmtPropAttribute(Attr);
            FmtType := FmtAttr.FormatType;
            Break;
          end;
        end;
      end;
    end;

    if FmtType = tiNone then
    begin
      UnderlyingType := Prop.PropertyType;

      if UnderlyingType.TypeKind = tkRecord then
      begin
        ValueField := UnderlyingType.GetField('Value');
        if Assigned(ValueField) then
          UnderlyingType := ValueField.FieldType;
      end;

      case UnderlyingType.TypeKind of
        tkInteger, tkInt64:
          FmtType := tiInteger;
        tkFloat:
          if UnderlyingType.Handle = TypeInfo(TDateTime) then
            FmtType := tiDateTime
          else
            FmtType := tiFloat;
        tkString, tkLString, tkWString, tkUString:
          FmtType := tiString;
        tkEnumeration:
          if UnderlyingType.Handle = TypeInfo(Boolean) then
            FmtType := tiBoolean
          else
            FmtType := tiString;
        tkVariant:
          FmtType := tiString;
      else
        FmtType := tiNone;
      end;
    end;

    if Value.Kind = tkRecord then
    begin
      UnderlyingType := Prop.PropertyType;
      HasValueField := UnderlyingType.GetField('HasValue');
      ValueField := UnderlyingType.GetField('Value');

      if Assigned(HasValueField) and Assigned(ValueField) then
      begin
        HasValue := HasValueField.GetValue(Value.GetReferenceToRawData).AsBoolean;
        if HasValue then
        begin
          ActualValue := ValueField.GetValue(Value.GetReferenceToRawData);
          Result := FormataInfo(ActualValue.AsVariant, FmtType);
        end
        else
          Result := '';
      end
      else
      begin
        if not Value.IsEmpty then
          Result := FormataInfo(Value.AsVariant, FmtType)
        else
          Result := '';
      end;
    end
    else
    begin
      if not Value.IsEmpty then
        Result := FormataInfo(Value.AsVariant, FmtType)
      else
        Result := '';
    end;
  finally
  end;
end;

procedure TQryRecInfo.Fetch(DBConn: TUniConnection; const QryTxt: string);
var
  qr: TUniQuery;
  Ctx: TRttiContext;
  ObjType: TRttiType;
  Prop: TRttiProperty;
  Field: TField;
  ORMFieldName: string;
begin
  if not Assigned(DBConn) or Trim(QryTxt).IsEmpty then
    Exit;

  qr := TUniQuery.Create(nil);
  try
    qr.Connection := DBConn;
    qr.SpecificOptions.Values['FetchAll'] := 'True';
    qr.SpecificOptions.Values['CreateConnection'] := 'False';
    qr.SQL.Text := QryTxt;
    qr.Open;

    if qr.RecordCount > 0 then
    begin
      Ctx := TRttiContext.Create;
      try
        ObjType := Ctx.GetType(Self.ClassType);

        for Prop in ObjType.GetProperties do
        begin
          if not Prop.IsWritable then
            Continue;

          Field := qr.FindField(Prop.Name);
          if not Assigned(Field) then
            Continue;

          ORMFieldName := '_' + Prop.Name;

          if Prop.PropertyType.TypeKind = tkRecord then
          begin
            if ObjType.GetField(ORMFieldName) = nil then
              Continue;

            if Prop.PropertyType.Name.StartsWith('TNullable<') then
            begin
              if Field.IsNull then
                ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<string>>(NullValue))
              else
              begin
                if Prop.PropertyType.Name = 'TNullable<System.string>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<string>>(Field.AsString))
                else if Prop.PropertyType.Name = 'TNullable<System.Integer>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Integer>>(Field.AsInteger))
                else if Prop.PropertyType.Name = 'TNullable<System.Int64>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Int64>>(Field.AsLargeInt))
                else if Prop.PropertyType.Name = 'TNullable<System.Extended>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Extended>>(Field.AsFloat))
                else if Prop.PropertyType.Name = 'TNullable<System.Currency>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Currency>>(Field.AsCurrency))
                else if Prop.PropertyType.Name = 'TNullable<System.TDateTime>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<TDateTime>>(Field.AsDateTime))
                else if Prop.PropertyType.Name = 'TNullable<System.Boolean>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Boolean>>(Field.AsBoolean))
                else if Prop.PropertyType.Name = 'TNullable<System.Variant>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Variant>>(Field.Value))
                else
                  raise EORMUtilsException.CreateFmt('Unsupported TNullable type: %s', [Prop.PropertyType.Name]);
              end;
            end
            else
              raise EORMUtilsException.CreateFmt('Unsupported record type: %s', [Prop.PropertyType.Name]);
          end
          else
          begin
            case Prop.PropertyType.TypeKind of
              tkString, tkLString, tkWString, tkUString:
                ObjType.GetField(ORMFieldName).SetValue(Self, Field.AsString);
              tkInteger:
                ObjType.GetField(ORMFieldName).SetValue(Self, Field.AsInteger);
              tkInt64:
                ObjType.GetField(ORMFieldName).SetValue(Self, Field.AsLargeInt);
              tkFloat:
                if Prop.PropertyType.Handle = TypeInfo(Currency) then
                  ObjType.GetField(ORMFieldName).SetValue(Self, Field.AsCurrency)
                else
                  ObjType.GetField(ORMFieldName).SetValue(Self, Field.AsFloat);
              tkVariant:
                ObjType.GetField(ORMFieldName).SetValue(Self, TValue.FromVariant(Field.AsVariant));
              tkEnumeration:
                if Prop.PropertyType.Handle = TypeInfo(Boolean) then
                  ObjType.GetField(ORMFieldName).SetValue(Self, Field.AsBoolean)
                else
                  raise EORMUtilsException.CreateFmt('Unsupported enumeration type: %s', [Prop.PropertyType.Name]);
            else
            end;
          end;
        end;

        ClearDirtyFlags;
      finally
      end;
    end;
  finally
    qr.Free;
  end;
end;

procedure TQryRecInfo.SetInfo(Index: Integer; const Value: TValue);
var
  PropName: string;
  PropKind: TTypeKind;
  Ctx: TRttiContext;
  Prop: TRttiProperty;
  Field: TRttiField;
  OldValue: TValue;
begin
  Ctx := TRttiContext.Create;
  try
    Prop := Ctx.GetType(Self.ClassType).GetProperties()[Index];
    if not Assigned(Prop) then
      raise EORMUtilsException.CreateFmt('Error: The index "%d" does not correspond to any property in class "%s".', [Index, ClassName]);

    PropName := Prop.Name;
    PropKind := Prop.PropertyType.TypeKind;

    Field := Ctx.GetType(Self.ClassType).GetField('_' + PropName);
    if not Assigned(Field) then
      raise EORMUtilsException.CreateFmt('Error: Field "_%s" not found in class "%s".', [PropName, ClassName]);

    if not FDirtyFields.ContainsKey(PropName) then
    begin
      OldValue := Field.GetValue(Self);
      FPreviousValues.AddOrSetValue(PropName, OldValue);
      FIsNewRec := True;
    end;

    Field.SetValue(Self, Value);
    FDirtyFields.AddOrSetValue(PropName, True);

    if Assigned(FOnPropertyChanged) then
      FOnPropertyChanged(Self, PropName);

    var UpdatedComp := ORMManager.FindComponent(Self, PropName);
    if Assigned(UpdatedComp) then
      ORMManager.UpdateComponentFromORM(UpdatedComp, ORMManager.GetORMRecForComponent(UpdatedComp), ORMManager.GetFieldNameForComponent(UpdatedComp));

  finally
  end;
end;

procedure TQryRecInfo.SetInfoCurrency(Index: Integer; const Value: Currency);
begin
  SetInfo(Index, TValue.From<Currency>(Value));
end;

procedure TQryRecInfo.SetInfoString(Index: Integer; const Value: string);
begin
  SetInfo(Index, TValue.From<string>(Value));
end;

procedure TQryRecInfo.SetInfoInteger(Index: Integer; const Value: Integer);
begin
  SetInfo(Index, TValue.From<Integer>(Value));
end;

procedure TQryRecInfo.SetInfoInt64(Index: Integer; const Value: Int64);
begin
  SetInfo(Index, TValue.From<Int64>(Value));
end;

procedure TQryRecInfo.SetInfoExtended(Index: Integer; const Value: Extended);
begin
  SetInfo(Index, TValue.From<Extended>(Value));
end;

procedure TQryRecInfo.SetInfoDateTime(Index: Integer; const Value: TDateTime);
begin
  SetInfo(Index, TValue.From<TDateTime>(Value));
end;

procedure TQryRecInfo.SetInfoBoolean(Index: Integer; const Value: Boolean);
begin
  SetInfo(Index, TValue.From<Boolean>(Value));
end;

procedure TQryRecInfo.SetInfoVariant(Index: Integer; const Value: Variant);
begin
  SetInfo(Index, TValue.From<Variant>(Value));
end;

procedure TQryRecInfo.SetInfoNullableBoolean(Index: Integer; const Value: TNullable<Boolean>);
begin
  SetInfo(Index, TValue.From<TNullable<Boolean>>(Value));
end;

procedure TQryRecInfo.SetInfoNullableCurrency(Index: Integer; const Value: TNullable<Currency>);
begin
  SetInfo(Index, TValue.From<TNullable<Currency>>(Value));
end;

procedure TQryRecInfo.SetInfoNullableDateTime(Index: Integer; const Value: TNullable<TDateTime>);
begin
  SetInfo(Index, TValue.From<TNullable<TDateTime>>(Value));
end;

procedure TQryRecInfo.SetInfoNullableExtended(Index: Integer; const Value: TNullable<Extended>);
begin
  SetInfo(Index, TValue.From<TNullable<Extended>>(Value));
end;

procedure TQryRecInfo.SetInfoNullableInteger(Index: Integer; const Value: TNullable<Integer>);
begin
  SetInfo(Index, TValue.From<TNullable<Integer>>(Value));
end;

procedure TQryRecInfo.SetInfoNullableInt64(Index: Integer; const Value: TNullable<Int64>);
begin
  SetInfo(Index, TValue.From<TNullable<Int64>>(Value));
end;

procedure TQryRecInfo.SetInfoNullableString(Index: Integer; const Value: TNullable<string>);
begin
  SetInfo(Index, TValue.From<TNullable<string>>(Value));
end;

procedure TQryRecInfo.SetInfoNullableVariant(Index: Integer; const Value: TNullable<Variant>);
begin
  SetInfo(Index, TValue.From<TNullable<Variant>>(Value));
end;

function TQryRecInfo.GetPropertyValue(const PropertyName: string): TValue;
var
  ctx: TRttiContext;
  rttiType: TRttiType;
  prop: TRttiProperty;
  UnderlyingType: TRttiType;
  Value: TValue;
  ValueField: TRttiField;
  HasValueField: TRttiField;
  HasValue: Boolean;
  ActualValue: TValue;
begin
  Result := TValue.Empty;

  ctx := TRttiContext.Create;
  try
    rttiType := ctx.GetType(Self.ClassType);
    prop := rttiType.GetProperty(PropertyName);
    if not Assigned(prop) then
      raise EORMUtilsException.CreateFmt('Property "%s" not found in class "%s".', [PropertyName, Self.ClassName]);

    Value := prop.GetValue(Self);

    if prop.PropertyType.TypeKind = tkRecord then
    begin
      ValueField := prop.PropertyType.GetField('Value');
      HasValueField := prop.PropertyType.GetField('HasValue');

      if Assigned(ValueField) and Assigned(HasValueField) then
      begin
        HasValue := HasValueField.GetValue(Value.GetReferenceToRawData).AsBoolean;
        if HasValue then
        begin
          ActualValue := ValueField.GetValue(Value.GetReferenceToRawData);
          Result := ActualValue;
        end
        else
        begin
          Result := TValue.Empty;
        end;
      end
      else
      begin
        Result := Value;
      end;
    end
    else
    begin
      Result := Value;
    end;
  finally
  end;
end;

constructor TQryRecArray<T>.Create;
begin
  inherited Create;
  FQryRecInfoList := TObjectList<T>.Create(True);
end;

destructor TQryRecArray<T>.Destroy;
begin
  FQryRecInfoList.Free;
  inherited;
end;

procedure TQryRecArray<T>.ClearFilter;
begin
  FFilterSQL := '';
end;

function TQryRecArray<T>.GetCount: Integer;
begin
  Result := FQryRecInfoList.Count;
end;

function TQryRecArray<T>.Rec(Index: Integer): T;
begin
  if (Index < 0) or (Index >= FQryRecInfoList.Count) then
    raise EORMUtilsException.CreateFmt('Index %d out of bounds.', [Index]);

  Result := FQryRecInfoList[Index];
end;

procedure TQryRecArray<T>.FetchAllInstance(DBConn: TUniConnection; const QryTxt: string);
var
  qr: TUniQuery;
  RecInstance: T;
  Ctx: TRttiContext;
  ObjType: TRttiType;
  Prop: TRttiProperty;
  Field: TField;
  ORMFieldName: string;
begin
  if not Assigned(DBConn) then
    raise EORMUtilsException.Create('Database connection (DBConn) is not assigned.');

  if Trim(QryTxt).IsEmpty then
    raise EORMUtilsException.Create('Query text (QryTxt) cannot be empty.');

  qr := TUniQuery.Create(nil);
  try
    qr.Connection := DBConn;
    qr.SpecificOptions.Values['FetchAll'] := 'True';
    qr.SpecificOptions.Values['CreateConnection'] := 'False';
    qr.SQL.Text := QryTxt;
    qr.Open;

    if qr.IsEmpty then
      Exit;

    Ctx := TRttiContext.Create;
    ObjType := Ctx.GetType(T);

    try
      while not qr.Eof do
      begin
        RecInstance := T.Create;
        try
          for Prop in ObjType.GetProperties do
          begin
            if not Prop.IsWritable then
              Continue;

            Field := qr.FindField(Prop.Name);
            if not Assigned(Field) then
              Continue;

            ORMFieldName := '_' + Prop.Name;

            if Prop.PropertyType.TypeKind = tkRecord then
            begin
              if ObjType.GetField(ORMFieldName) = nil then
                Continue;

              if Prop.PropertyType.Name.StartsWith('TNullable<') then
              begin
                if Field.IsNull then
                  ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), TValue.From<TNullable<string>>(NullValue))
                else
                begin
                  if Prop.PropertyType.Name = 'TNullable<System.string>' then
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), TValue.From<TNullable<string>>(Field.AsString))
                  else if Prop.PropertyType.Name = 'TNullable<System.Integer>' then
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), TValue.From<TNullable<Integer>>(Field.AsInteger))
                  else if Prop.PropertyType.Name = 'TNullable<System.Int64>' then
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), TValue.From<TNullable<Int64>>(Field.AsLargeInt))
                  else if Prop.PropertyType.Name = 'TNullable<System.Extended>' then
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), TValue.From<TNullable<Extended>>(Field.AsFloat))
                  else if Prop.PropertyType.Name = 'TNullable<System.Currency>' then
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), TValue.From<TNullable<Currency>>(Field.AsCurrency))
                  else if Prop.PropertyType.Name = 'TNullable<System.TDateTime>' then
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), TValue.From<TNullable<TDateTime>>(Field.AsDateTime))
                  else if Prop.PropertyType.Name = 'TNullable<System.Boolean>' then
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), TValue.From<TNullable<Boolean>>(Field.AsBoolean))
                  else if Prop.PropertyType.Name = 'TNullable<System.Variant>' then
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), TValue.From<TNullable<Variant>>(Field.Value))
                  else
                    raise EORMUtilsException.CreateFmt('Unsupported TNullable type: %s', [Prop.PropertyType.Name]);
                end;
              end
              else
                raise EORMUtilsException.CreateFmt('Unsupported record type: %s', [Prop.PropertyType.Name]);
            end
            else
            begin
              case Prop.PropertyType.TypeKind of
                tkString, tkLString, tkWString, tkUString:
                  ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), Field.AsString);
                tkInteger:
                  ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), Field.AsInteger);
                tkInt64:
                  ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), Field.AsLargeInt);
                tkFloat:
                  if Prop.PropertyType.Handle = TypeInfo(Currency) then
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), Field.AsCurrency)
                  else
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), Field.AsFloat);
                tkVariant:
                  ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), TValue.FromVariant(Field.AsVariant));
                tkEnumeration:
                  if Prop.PropertyType.Handle = TypeInfo(Boolean) then
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), Field.AsBoolean)
                  else
                    ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), Field.AsString);
              else
              end;
            end;
          end;

          RecInstance.ClearDirtyFlags;
          FQryRecInfoList.Add(RecInstance);
        except
          RecInstance.Free;
          raise;
        end;
        qr.Next;
      end;
    finally
    end;
  finally
    qr.Free;
  end;
end;

function TQryRecArray<T>.GetEnumerator: TEnumerator<T>;
begin
  Result := FQryRecInfoList.GetEnumerator;
end;

class function TQryRecArray<T>.FetchAll(DBConn: TUniConnection; const QryTxt: string): TQryRecArray<T>;
var
  Instance: TQryRecArray<T>;
begin
  Instance := TQryRecArray<T>.Create;
  try
    Instance.FetchAllInstance(DBConn, QryTxt);
    Result := Instance;
  except
    Instance.Free;
    raise;
  end;
end;

initialization
  ORMManager := TORMManager.Create;

finalization
  ORMManager.Free;

end.

--------

unit orm_binding;

interface

uses
  System.Classes,
  System.Rtti,
  System.Generics.Collections,
  orm_utils,
  AdvCGrid,
  Variants,
  SysUtils,
  StrUtils,
  Dialogs,
  pixel_classes,
  interposer_class;

type
  TORMBinding = class
  private
    FGrid: TAdvColumnGrid;
    FData: TQryRecArray<TQryRecInfo>;
    FColumnFields: TDictionary<Integer, string>;
    FColumnTypes: TDictionary<string, TRttiType>;
    FRowMapping: TDictionary<Integer, TQryRecInfo>;
    FCtx: TRttiContext;

    procedure BindColumns;
    procedure PopulateAndMapGrid;
    procedure SetupEventHandlers;
    procedure GridCellChanged(Sender: TObject; ACol, ARow: Integer);
    procedure ORMPropertyChanged(Sender: TQryRecInfo; const PropertyName: string);
    procedure SetORMProperty(ORMRec: TQryRecInfo; const FieldName: string; const Value: TValue);
    procedure UpdateGridCell(ACol: Integer; ARow: Integer);
  public
    constructor Create(AGrid: TAdvColumnGrid; AData: TQryRecArray<TQryRecInfo>);
    destructor Destroy; override;
    procedure InitializeBinding;
  end;

implementation

{ TORMBinding }

constructor TORMBinding.Create(AGrid: TAdvColumnGrid; AData: TQryRecArray<TQryRecInfo>);
begin
  inherited Create;
  FGrid := AGrid;
  FData := AData;
  FColumnFields := TDictionary<Integer, string>.Create;
  FColumnTypes := TDictionary<string, TRttiType>.Create;
  FRowMapping := TDictionary<Integer, TQryRecInfo>.Create;
  FCtx := TRttiContext.Create;

  BindColumns;
  InitializeBinding;
  SetupEventHandlers;
end;

destructor TORMBinding.Destroy;
var
  ORMRec: TQryRecInfo;
begin
  for ORMRec in FRowMapping.Values do
    ORMRec.OnPropertyChanged := nil;

  FRowMapping.Free;
  FColumnFields.Free;
  FColumnTypes.Free;
  FCtx.Free;
  inherited;
end;

procedure TORMBinding.BindColumns;
var
  i: Integer;
  PropName: string;
  RttiProp: TRttiProperty;
  ORMRec: TQryRecInfo;
begin
  if FData.RecCount = 0 then
    Exit;

  ORMRec := FData.Rec(0);

  for i := 0 to FGrid.ColCount - 1 do
  begin
    PropName := FGrid.ORMField[i];
    if PropName <> '' then
    begin
      if not FColumnFields.ContainsKey(i) then
        FColumnFields.Add(i, PropName)
      else
        FColumnFields[i] := PropName;

      RttiProp := FCtx.GetType(ORMRec.ClassType).GetProperty(PropName);
      if Assigned(RttiProp) then
        FColumnTypes.AddOrSetValue(PropName, RttiProp.PropertyType)
      else
        raise Exception.CreateFmt('Property "%s" not found in class "%s".', [PropName, ORMRec.ClassName]);
    end;
  end;
end;

procedure TORMBinding.InitializeBinding;
begin
  PopulateAndMapGrid;
end;

procedure TORMBinding.PopulateAndMapGrid;
var
  i, j: Integer;
  ORMRec: TQryRecInfo;
  PropName: string;
  RttiProp: TRttiProperty;
  Value: TValue;
begin
  FGrid.RowCount := FData.RecCount + 1;
  FRowMapping.Clear;

  for i := 0 to FData.RecCount - 1 do
  begin
    ORMRec := FData.Rec(i);
    FRowMapping.Add(i, ORMRec);

    ORMRec.OnPropertyChanged := ORMPropertyChanged;

    for j := 0 to FGrid.ColCount - 1 do
    begin
      if FColumnFields.TryGetValue(j, PropName) then
      begin
        RttiProp := FCtx.GetType(ORMRec.ClassType).GetProperty(PropName);
        if Assigned(RttiProp) then
        begin
          Value := ORMRec.GetPropertyValue(PropName);
          case RttiProp.PropertyType.TypeKind of
            tkInteger, tkInt64:
              FGrid.Cells[j, i + 1] := Value.ToString;
            tkFloat:
              FGrid.Cells[j, i + 1] := FloatToStr(Value.AsExtended);
            tkString, tkUString, tkLString, tkWString:
              FGrid.Cells[j, i + 1] := Value.AsString;
            tkEnumeration:
              if RttiProp.PropertyType.Handle = TypeInfo(Boolean) then
                FGrid.Cells[j, i + 1] := IfThen(Value.AsBoolean, 'True', 'False')
              else
                FGrid.Cells[j, i + 1] := Value.ToString;
            tkVariant:
              FGrid.Cells[j, i + 1] := VarToStr(Value.AsVariant);
            else
              FGrid.Cells[j, i + 1] := Value.ToString;
          end;
        end
        else
          FGrid.Cells[j, i + 1] := '';
      end
      else
        FGrid.Cells[j, i + 1] := '';
    end;
  end;
end;

procedure TORMBinding.SetupEventHandlers;
begin
  FGrid.OnEditCellDone := GridCellChanged;
end;

procedure TORMBinding.GridCellChanged(Sender: TObject; ACol, ARow: Integer);
var
  NewValue: string;
  ORMRec: TQryRecInfo;
  PropName: string;
  RttiProp: TRttiProperty;
  TValueToSet: TValue;
begin
  if ARow < 1 then Exit;

  if not FRowMapping.TryGetValue(ARow - 1, ORMRec) then Exit;
  if not FColumnFields.TryGetValue(ACol, PropName) then Exit;

  RttiProp := FCtx.GetType(ORMRec.ClassType).GetProperty(PropName);
  if not Assigned(RttiProp) then Exit;

  NewValue := FGrid.Cells[ACol, ARow];

  case RttiProp.PropertyType.TypeKind of
    tkInteger:
      TValueToSet := TValue.From<Integer>(StrToIntDef(NewValue, 0));
    tkInt64:
      TValueToSet := TValue.From<Int64>(StrToInt64Def(NewValue, 0));
    tkFloat:
      TValueToSet := TValue.From<Extended>(StrToFloatDef(NewValue, 0.0));
    tkString, tkUString, tkLString, tkWString:
      TValueToSet := TValue.From<string>(NewValue);
    tkEnumeration:
      if RttiProp.PropertyType.Handle = TypeInfo(Boolean) then
        TValueToSet := TValue.From<Boolean>(SameText(NewValue, 'True') or SameText(NewValue, '1'))
      else
        TValueToSet := TValue.From<string>(NewValue);
    tkVariant:
      TValueToSet := TValue.FromVariant(NewValue);
    else
      TValueToSet := TValue.From<string>(NewValue);
  end;

  SetORMProperty(ORMRec, PropName, TValueToSet);
end;

procedure TORMBinding.ORMPropertyChanged(Sender: TQryRecInfo; const PropertyName: string);
var
  ARow: Integer;
  ACol: Integer;
  RttiProp: TRttiProperty;
  Value: TValue;
begin
  ARow := -1;
  for var Key in FRowMapping.Keys do
  begin
    if FRowMapping[Key] = Sender then
    begin
      ARow := Key;
      Break;
    end;
  end;

  if ARow = -1 then Exit;

  ACol := -1;
  for var Col in FColumnFields.Keys do
  begin
    if SameText(FColumnFields[Col], PropertyName) then
    begin
      ACol := Col;
      Break;
    end;
  end;

  if ACol = -1 then Exit;

  RttiProp := FCtx.GetType(Sender.ClassType).GetProperty(PropertyName);
  if not Assigned(RttiProp) then Exit;

  Value := Sender.GetPropertyValue(PropertyName);

  case RttiProp.PropertyType.TypeKind of
    tkInteger, tkInt64:
      FGrid.Cells[ACol, ARow + 1] := Value.ToString;
    tkFloat:
      FGrid.Cells[ACol, ARow + 1] := FloatToStr(Value.AsExtended);
    tkString, tkUString, tkLString, tkWString:
      FGrid.Cells[ACol, ARow + 1] := Value.AsString;
    tkEnumeration:
      if RttiProp.PropertyType.Handle = TypeInfo(Boolean) then
        FGrid.Cells[ACol, ARow + 1] := IfThen(Value.AsBoolean, 'True', 'False')
      else
        FGrid.Cells[ACol, ARow + 1] := Value.ToString;
    tkVariant:
      FGrid.Cells[ACol, ARow + 1] := VarToStr(Value.AsVariant);
    else
      FGrid.Cells[ACol, ARow + 1] := Value.ToString;
  end;
end;

procedure TORMBinding.SetORMProperty(ORMRec: TQryRecInfo; const FieldName: string; const Value: TValue);
var
  Ctx: TRttiContext;
  Prop: TRttiProperty;
  ORMFieldName: string;
  ObjType: TRttiType;
begin
  Ctx := TRttiContext.Create;
  try
    ObjType := Ctx.GetType(ORMRec.ClassType);
    Prop := ObjType.GetProperty(FieldName);

    if Assigned(Prop) and Prop.IsWritable then
    begin
      ORMFieldName := '_' + FieldName;

      if ObjType.GetField(ORMFieldName) = nil then
        Exit;

      if Prop.PropertyType.TypeKind = tkRecord then
      begin
        if Prop.PropertyType.Name.StartsWith('TNullable<') then
        begin
          if Value.IsEmpty or VarIsNull(Value.AsVariant) then
          begin
            if Prop.PropertyType.Name = 'TNullable<System.string>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<string>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Integer>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Integer>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Int64>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Int64>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Extended>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Extended>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Currency>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Currency>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.TDateTime>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<TDateTime>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Boolean>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Boolean>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Variant>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Variant>>(NullValue));
          end
          else
          begin
            if Prop.PropertyType.Name = 'TNullable<System.string>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<string>>(Value.AsString))
            else if Prop.PropertyType.Name = 'TNullable<System.Integer>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Integer>>(Value.AsInteger))
            else if Prop.PropertyType.Name = 'TNullable<System.Int64>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Int64>>(Value.AsInt64))
            else if Prop.PropertyType.Name = 'TNullable<System.Extended>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Extended>>(Value.AsExtended))
            else if Prop.PropertyType.Name = 'TNullable<System.Currency>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Currency>>(Value.AsCurrency))
            else if Prop.PropertyType.Name = 'TNullable<System.TDateTime>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<TDateTime>>(Value.AsType<TDateTime>))
            else if Prop.PropertyType.Name = 'TNullable<System.Boolean>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Boolean>>(Value.AsBoolean))
          end;
        end
        else
          raise EORMUtilsException.CreateFmt('Unsupported record type: %s', [Prop.PropertyType.Name]);
      end
      else
      begin
        case Prop.PropertyType.TypeKind of
          tkString, tkLString, tkWString, tkUString:
            ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsString);
          tkInteger:
            ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsInteger);
          tkInt64:
            ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsInt64);
          tkFloat:
            if Prop.PropertyType.Handle = TypeInfo(Currency) then
              ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsCurrency)
            else if Prop.PropertyType.Handle = TypeInfo(TDateTime) then
              ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), TValue.From<TDateTime>(Value.AsType<TDateTime>))
            else
              ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsExtended);
          tkEnumeration:
            if Prop.PropertyType.Handle = TypeInfo(Boolean) then
              ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsBoolean)
            else
              ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsString);
        else
          raise EORMUtilsException.CreateFmt('Unsupported type for field "%s" in record "%s".', [FieldName, ORMRec.ClassName]);
        end;
      end;

      var Component := ORMManager.FindComponent(ORMRec, FieldName);
      if Assigned(Component) then
      begin
        ORMManager.UpdateComponentFromORM(Component, ORMRec, FieldName);
      end;

    end
    else
      raise EORMUtilsException.CreateFmt('Property "%s" not found or not writable in record "%s".', [FieldName, ORMRec.ClassName]);
  finally
  end;
end;

end.

--------------

Grid event where the logic must be done:
procedure TMainForm.GridNomesEditCellDone(Sender: TObject; ACol, ARow: Integer);

    Core Improvements:
    Implement event handlers for validation and editing control

    New Features:
    Record management (add, delete, move)


I will share this question with another LLM. Please remove the implemmentations inside the units that art nor relevant form my questions