Review and check the code below:

After carefully reviewing your Delphi unit orm_utils, I've identified and addressed several issues to ensure the code functions correctly and adheres to best practices. Below are the key fixes and improvements made:

    Incorrect Use of TRttiContext.Free:
        TRttiContext is a record, not a class. Therefore, calling Free on it is incorrect and has been removed from all relevant procedures.

    Improper Handling of RecInstance:
        In the SetValue method, Pointer(RecInstance) was incorrectly used. It has been replaced with RecInstance to correctly reference the object instance.

    Type Name Comparisons:
        Comparing type names as strings (e.g., 'TNullable<System.string>') can be fragile. Consider using RTTI type comparisons or generics for more robust type handling.

    Event Handling to Prevent Recursion:
        Added a flag to prevent recursive updates when binding components to properties, ensuring that updates don't trigger each other indefinitely.

    Additional Enhancements:
        Improved error handling and added comments for better readability and maintainability.
        Ensured that all RTTI operations correctly reference object instances without typecasting to pointers.

Below is the revised and fixed version of your orm_utils unit:

unit orm_utils;

interface

uses
  System.SysUtils,
  System.Classes,
  System.Generics.Collections,
  System.Rtti,
  System.TypInfo,
  Data.DB,
  Uni,
  pixel_classes,
  orm_customattributes,
  Vcl.ComCtrls,
  Vcl.StdCtrls,
  Vcl.ExtCtrls;

type
  EORMUtilsException = class(Exception);

  TQryRecInfo = class;

  TBindDirection = (bdTwoWay, bdToComponent, bdFromComponent);

  TComponentBindingInfo = record
    Component: TComponent;
    PropertyName: string;
    Direction: TBindDirection;
  end;

  TBindingDictionary = class(TDictionary<string, TComponentBindingInfo>)
  private
    FOwner: TQryRecInfo;
  public
    constructor Create(AOwner: TQryRecInfo);
    procedure UpdateComponents;
    procedure UpdateProperties;
  end;

  /// <summary>
  /// Base class for query record information
  /// Use descendants of this class to represent specific database records.
  /// </summary>
  TQryRecInfo = class
  private
    FDirtyFields: TDictionary<string, Boolean>;
    FPreviousValues: TDictionary<string, TValue>;
    FBindings: TBindingDictionary;
    FUpdating: Boolean; // Flag to prevent recursive updates
    procedure ComponentChangeHandler(Sender: TObject);
    procedure SetInfo(Index: Integer; const Value: TValue);
  public
    constructor Create;
    destructor Destroy; override;

    // Methods to set nullable fields
    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>);

    // Methods to set non-nullable simple types
    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);

    // Methods to handle dirty fields
    function IsFieldDirty(const FieldName: string): Boolean;
    procedure ClearDirtyFlags;
    property DirtyFields: TDictionary<string, Boolean> read FDirtyFields;

    // Methods to handle previous values
    function GetPreviousValue(const FieldName: string): TValue;
    procedure ClearPreviousValues;
    property PreviousValues: TDictionary<string, TValue> read FPreviousValues;

    /// <summary>
    /// Retrieves the formatted text for a given field based on the FmtProp attribute.
    /// </summary>
    function GetFormattedText(const FieldName: string): string;

    /// <summary>
    /// Fetches data for this record from the database based on the provided SQL.
    /// </summary>
    procedure Fetch(DBConn: TUniConnection; const QryTxt: string);

    /// <summary>
    /// Binds a property to a UI component.
    /// </summary>
    procedure Bind(const PropertyName: string; Component: TComponent; Direction: TBindDirection = bdTwoWay);
    /// <summary>
    /// Binds properties based on component hints.
    /// </summary>
    procedure BindByHint;
    /// <summary>
    /// Unbinds a property or a component.
    /// </summary>
    procedure Unbind(const PropertyName: string); overload;
    procedure Unbind(Component: TComponent); overload;
    /// <summary>
    /// Unbinds all bindings.
    /// </summary>
    procedure UnbindAll;
    property Bindings: TBindingDictionary read FBindings;
  end;

  /// <summary>
  /// Generic class to handle arrays of TQryRecInfo descendants.
  /// </summary>
  TQryRecArray<T: class, constructor> = class
  private
    FQryRecInfoList: TObjectList<T>;
    FFilterSQL: string;
    FDBConn: TUniConnection;
    function GetCount: Integer;
  public
    constructor Create;
    destructor Destroy; override;

    /// <summary>
    /// Fetches all records based on the provided SQL text and populates the array.
    /// </summary>
    /// <param name="QryTxt">The base SQL query text.</param>
    procedure FetchAllInstance(DBConn: TUniConnection; const QryTxt: string);

    /// <summary>
    /// Clears any SQL filters applied.
    /// </summary>
    procedure ClearFilter;

    /// <summary>
    /// Accesses a record at the specified index.
    /// </summary>
    /// <param name="Index">The zero-based index of the record.</param>
    /// <returns>The record at the specified index.</returns>
    function Rec(Index: Integer): T;

    /// <summary>
    /// Number of records in the array.
    /// </summary>
    property RecCount: Integer read GetCount;

    /// <summary>
    /// Database connection used for fetching records.
    /// </summary>
    property DBConn: TUniConnection read FDBConn write FDBConn;

    /// <summary>
    /// Additional SQL filter to apply to the query.
    /// </summary>
    property SQLFilter: string read FFilterSQL write FFilterSQL;

    /// <summary>
    /// Provides support for for-in loops by returning an enumerator.
    /// </summary>
    function GetEnumerator: TEnumerator<T>;

    /// <summary>
    /// Factory method to create and fetch records in a single step.
    /// </summary>
    /// <param name="DBConn">Database connection.</param>
    /// <param name="QryTxt">SQL query text.</param>
    /// <returns>Populated instance of TQryRecArray<T>.</returns>
    class function FetchAll(DBConn: TUniConnection; const QryTxt: string): TQryRecArray<T>; static;
  end;

implementation

uses
  System.Variants,
  StrUtils,
  pixel_utils, // Ensure this unit is in your project and properly implemented
  Vcl.Forms; // Added for TControl to retrieve Owner in BindByHint

{ TQryRecInfo }

constructor TQryRecInfo.Create;
begin
  inherited Create;
  FDirtyFields := TDictionary<string, Boolean>.Create;
  FPreviousValues := TDictionary<string, TValue>.Create;
  FBindings := TBindingDictionary.Create(Self);
  FUpdating := False;
end;

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

procedure TQryRecInfo.ClearDirtyFlags;
begin
  FDirtyFields.Clear;
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 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;

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);

    // Find the property by name
    Prop := RttiType.GetProperty(FieldName);
    if not Assigned(Prop) then
      raise EORMUtilsException.CreateFmt('Property "%s" not found.', [FieldName]);

    // Get the value of the property
    Value := Prop.GetValue(Self);
    FmtType := tiNone;

    // Check for FmtProp attribute on the property
    for Attr in Prop.GetAttributes do
    begin
      if Attr is FmtPropAttribute then
      begin
        FmtAttr := FmtPropAttribute(Attr);
        FmtType := FmtAttr.FormatType;
        Break;
      end;
    end;

    // If not found on property, check on the corresponding field
    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;

    // Determine default FmtType based on property type if not set by attribute
    if FmtType = tiNone then
    begin
      UnderlyingType := Prop.PropertyType;

      // Handle TNullable<T>
      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;

    // Extract actual value from TNullable<T> if necessary
    if Value.Kind = tkRecord then
    begin
      // Assuming TNullable<T> has 'HasValue' and 'Value' fields
      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);
          // Format the actual value
          Result := FormataInfo(ActualValue.AsVariant, FmtType);
        end
        else
          Result := '';
      end
      else
      begin
        // Not TNullable<T>, handle value directly
        if not Value.IsEmpty then
          Result := FormataInfo(Value.AsVariant, FmtType)
        else
          Result := '';
      end;
    end
    else
    begin
      // Not a record, handle value directly
      if not Value.IsEmpty then
        Result := FormataInfo(Value.AsVariant, FmtType)
      else
        Result := '';
    end;
  finally
    // No need to free TRttiContext as it's a record
  end;
end;

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

  qr := TUniQuery.Create(nil);
  try
    qr.Connection := DBConn;
    // SpecificOptions might vary based on your database components
    qr.SQL.Text := QryTxt;
    qr.Open;

    if not qr.IsEmpty then
    begin
      Ctx := TRttiContext.Create;
      try
        ObjType := Ctx.GetType(Self.ClassType);

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

          // Attempt to find the corresponding field in the query
          Field := qr.FindField(Prop.Name);
          if not Assigned(Field) then
            Continue; // Field not found, skip

          ORMFieldName := '_' + Prop.Name;

          // Handle TNullable<T> types
          if Prop.PropertyType.TypeKind = tkRecord then
          begin
            // Expecting TNullable<T>
            // Attempt to find the backing field for the nullable property
            if ObjType.GetField(ORMFieldName) = nil then
              Continue; // Backing field not found, skip

            NullableTypeName := Prop.PropertyType.Name;

            if NullableTypeName.StartsWith('TNullable<') then
            begin
              // Use RTTI to determine the generic type parameter
              if Field.IsNull then
                ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Variant>>(NullValue))
              else
              begin
                if NullableTypeName = 'TNullable<string>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<string>>(Field.AsString))
                else if NullableTypeName = 'TNullable<Integer>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Integer>>(Field.AsInteger))
                else if NullableTypeName = 'TNullable<Int64>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Int64>>(Field.AsLargeInt))
                else if NullableTypeName = 'TNullable<Extended>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Extended>>(Field.AsFloat))
                else if NullableTypeName = 'TNullable<Currency>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Currency>>(Field.AsCurrency))
                else if NullableTypeName = 'TNullable<TDateTime>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<TDateTime>>(Field.AsDateTime))
                else if NullableTypeName = 'TNullable<Boolean>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Boolean>>(Field.AsBoolean))
                else if NullableTypeName = 'TNullable<Variant>' then
                  ObjType.GetField(ORMFieldName).SetValue(Self, TValue.From<TNullable<Variant>>(Field.Value))
                else
                  raise EORMUtilsException.CreateFmt('Unsupported TNullable type: %s', [NullableTypeName]);
              end;
            end
            else
              raise EORMUtilsException.CreateFmt('Unsupported record type: %s', [Prop.PropertyType.Name]);
          end
          else
          begin
            // Handle non-nullable types
            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
                  ObjType.GetField(ORMFieldName).SetValue(Self, Field.AsString);
            else
              // Add additional handling for other types if necessary
            end;
          end;
        end;
      finally
        // No need to free TRttiContext as it's a record
      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
  // Prevent recursive updates
  if FUpdating then
    Exit;

  FUpdating := True;
  try
    // Retrieve property name and kind based on index
    // Assuming that the index corresponds to the property order
    Ctx := TRttiContext.Create;
    try
      if (Index < 0) or (Index >= Ctx.GetType(Self.ClassType).GetProperties.Length) then
        raise EORMUtilsException.CreateFmt('Error: The index "%d" does not correspond to any property in class "%s".', [Index, ClassName]);

      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 the field is not yet dirty, store the previous value
      if not FDirtyFields.ContainsKey(PropName) then
      begin
        OldValue := Field.GetValue(Self);
        FPreviousValues.AddOrSetValue(PropName, OldValue);
      end;

      // Set the new value
      Field.SetValue(Self, Value);

      // Mark the property as dirty
      FDirtyFields.AddOrSetValue(PropName, True);
    finally
      // No need to free TRttiContext as it's a record
    end;
  finally
    FUpdating := False;
  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;

{ TQryRecArray<T> }

constructor TQryRecArray<T>.Create;
begin
  inherited Create;
  FQryRecInfoList := TObjectList<T>.Create(True); // Owns objects
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;
  NullableTypeName: string;
  FieldValue: TValue;
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;
    // SpecificOptions might vary based on your database components
    qr.SQL.Text := QryTxt;
    qr.Open;

    if qr.IsEmpty then
      Exit; // No records to fetch

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

    try
      while not qr.Eof do
      begin
        RecInstance := T.Create;
        try
          // Iterate over each property of T and assign values from the query
          for Prop in ObjType.GetProperties do
          begin
            if not Prop.IsWritable then
              Continue;

            // Attempt to find the corresponding field in the query
            Field := qr.FindField(Prop.Name);
            if not Assigned(Field) then
              Continue; // Field not found, skip

            ORMFieldName := '_' + Prop.Name;

            // Handle TNullable<T> types
            if Prop.PropertyType.TypeKind = tkRecord then
            begin
              // Expecting TNullable<T>
              // Attempt to find the backing field for the nullable property
              if ObjType.GetField(ORMFieldName) = nil then
                Continue; // Backing field not found, skip

              NullableTypeName := Prop.PropertyType.Name;

              if NullableTypeName.StartsWith('TNullable<') then
              begin
                // Use RTTI to determine the generic type parameter
                if Field.IsNull then
                  ObjType.GetField(ORMFieldName).SetValue(RecInstance, TValue.From<TNullable<Variant>>(NullValue))
                else
                begin
                  // Determine the generic type and set the value accordingly

                  if NullableTypeName = 'TNullable<string>' then
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, TValue.From<TNullable<string>>(Field.AsString))
                  else if NullableTypeName = 'TNullable<Integer>' then
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, TValue.From<TNullable<Integer>>(Field.AsInteger))
                  else if NullableTypeName = 'TNullable<Int64>' then
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, TValue.From<TNullable<Int64>>(Field.AsLargeInt))
                  else if NullableTypeName = 'TNullable<Extended>' then
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, TValue.From<TNullable<Extended>>(Field.AsFloat))
                  else if NullableTypeName = 'TNullable<Currency>' then
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, TValue.From<TNullable<Currency>>(Field.AsCurrency))
                  else if NullableTypeName = 'TNullable<TDateTime>' then
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, TValue.From<TNullable<TDateTime>>(Field.AsDateTime))
                  else if NullableTypeName = 'TNullable<Boolean>' then
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, TValue.From<TNullable<Boolean>>(Field.AsBoolean))
                  else if NullableTypeName = 'TNullable<Variant>' then
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, TValue.From<TNullable<Variant>>(Field.Value))
                  else
                    raise EORMUtilsException.CreateFmt('Unsupported TNullable type: %s', [NullableTypeName]);
                end;
              end
              else
                raise EORMUtilsException.CreateFmt('Unsupported record type: %s', [Prop.PropertyType.Name]);
            end
            else
            begin
              // Handle non-nullable types
              case Prop.PropertyType.TypeKind of
                tkString, tkLString, tkWString, tkUString:
                  ObjType.GetField(ORMFieldName).SetValue(RecInstance, Field.AsString);
                tkInteger:
                  ObjType.GetField(ORMFieldName).SetValue(RecInstance, Field.AsInteger);
                tkInt64:
                  ObjType.GetField(ORMFieldName).SetValue(RecInstance, Field.AsLargeInt);
                tkFloat:
                  if Prop.PropertyType.Handle = TypeInfo(Currency) then
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, Field.AsCurrency)
                  else if Prop.PropertyType.Handle = TypeInfo(TDateTime) then
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, Field.AsDateTime)
                  else
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, Field.AsFloat);
                tkVariant:
                  ObjType.GetField(ORMFieldName).SetValue(RecInstance, TValue.FromVariant(Field.AsVariant));
                tkEnumeration:
                  if Prop.PropertyType.Handle = TypeInfo(Boolean) then
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, Field.AsBoolean)
                  else
                    ObjType.GetField(ORMFieldName).SetValue(RecInstance, Field.AsString);
              else
                // Unsupported type; you can extend this as needed
                Continue;
              end;
            end;
          end;

          // Add the populated record to the list
          FQryRecInfoList.Add(RecInstance);
        except
          RecInstance.Free;
          raise; // Re-raise the exception after cleanup
        end;
        qr.Next;
      end;
    finally
      // No need to free TRttiContext as it's a record
    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;

{ TBindingDictionary }

constructor TBindingDictionary.Create(AOwner: TQryRecInfo);
begin
  inherited Create;
  FOwner := AOwner;
end;

procedure TBindingDictionary.UpdateComponents;
var
  Pair: TPair<string, TComponentBindingInfo>;
  PropValue: string;
  Component: TComponent;
  PropInfo: PPropInfo;
begin
  for Pair in Self do
  begin
    if not (Pair.Value.Direction in [bdTwoWay, bdToComponent]) then
      Continue;

    Component := Pair.Value.Component;
    if not Assigned(Component) then
      Continue;

    // Get formatted text value from the property
    PropValue := FOwner.GetFormattedText(Pair.Key);

    // Set component property based on its type
    PropInfo := GetPropInfo(Component.ClassInfo, Pair.Value.PropertyName);
    if not Assigned(PropInfo) then
      Continue;

    try
      case PropInfo^.PropType^.Kind of
        tkString, tkLString, tkWString, tkUString:
          SetStrProp(Component, PropInfo, PropValue);
        tkFloat:
          if PropInfo^.PropType^ = TypeInfo(TDateTime) then
            SetFloatProp(Component, PropInfo, StrToDateTimeDef(PropValue, 0))
          else
            SetFloatProp(Component, PropInfo, StrToFloatDef(PropValue, 0));
        tkInteger:
          SetOrdProp(Component, PropInfo, StrToIntDef(PropValue, 0));
        tkInt64:
          SetInt64Prop(Component, PropInfo, StrToInt64Def(PropValue, 0));
        tkVariant:
          SetVariantProp(Component, PropInfo, PropValue);
      end;
    except
      on E: Exception do
        raise EORMUtilsException.CreateFmt('Error updating component property "%s": %s', [Pair.Key, E.Message]);
    end;
  end;
end;

procedure TBindingDictionary.UpdateProperties;
var
  Pair: TPair<string, TComponentBindingInfo>;
  Component: TComponent;
  PropInfo: PPropInfo;
  PropValue: string;
  Context: TRttiContext;
  RttiType: TRttiType;
  RttiProp: TRttiProperty;
begin
  Context := TRttiContext.Create;
  try
    RttiType := Context.GetType(FOwner.ClassType);

    for Pair in Self do
    begin
      if not (Pair.Value.Direction in [bdTwoWay, bdFromComponent]) then
        Continue;

      Component := Pair.Value.Component;
      if not Assigned(Component) then
        Continue;

      // Get component property value
      PropInfo := GetPropInfo(Component.ClassInfo, Pair.Value.PropertyName);
      if not Assigned(PropInfo) then
        Continue;

      try
        case PropInfo^.PropType^.Kind of
          tkString, tkLString, tkWString, tkUString:
            PropValue := GetStrProp(Component, PropInfo);
          tkFloat:
            if PropInfo^.PropType^ = TypeInfo(TDateTime) then
              PropValue := DateTimeToStr(GetFloatProp(Component, PropInfo))
            else
              PropValue := FloatToStr(GetFloatProp(Component, PropInfo));
          tkInteger:
            PropValue := IntToStr(GetOrdProp(Component, PropInfo));
          tkInt64:
            PropValue := IntToStr(GetInt64Prop(Component, PropInfo));
          tkVariant:
            PropValue := VarToStr(GetVariantProp(Component, PropInfo));
        else
          Continue;
        end;
      except
        on E: Exception do
          raise EORMUtilsException.CreateFmt('Error reading component property "%s": %s', [Pair.Key, E.Message]);
      end;

      // Find and update the property using RTTI
      RttiProp := RttiType.GetProperty(Pair.Key);
      if Assigned(RttiProp) and RttiProp.IsWritable then
      begin
        try
          case RttiProp.PropertyType.TypeKind of
            tkString, tkLString, tkWString, tkUString:
              RttiProp.SetValue(FOwner, PropValue);
            tkInteger:
              RttiProp.SetValue(FOwner, StrToIntDef(PropValue, 0));
            tkInt64:
              RttiProp.SetValue(FOwner, StrToInt64Def(PropValue, 0));
            tkFloat:
              if RttiProp.PropertyType.Handle = TypeInfo(TDateTime) then
                RttiProp.SetValue(FOwner, StrToDateTimeDef(PropValue, 0))
              else if RttiProp.PropertyType.Handle = TypeInfo(Currency) then
                RttiProp.SetValue(FOwner, StrToCurrDef(PropValue, 0))
              else
                RttiProp.SetValue(FOwner, StrToFloatDef(PropValue, 0));
            tkVariant:
              RttiProp.SetValue(FOwner, PropValue);
          end;
        except
          on E: Exception do
            raise EORMUtilsException.CreateFmt('Error updating property "%s": %s', [Pair.Key, E.Message]);
        end;
      end;
    end;
  finally
    // No need to free TRttiContext as it's a record
  end;
end;

{ Prevent Recursive Updates }

procedure TQryRecInfo.ComponentChangeHandler(Sender: TObject);
begin
  if FUpdating then
    Exit;

  if Sender is TComponent then
  begin
    FUpdating := True;
    try
      FBindings.UpdateProperties;
    finally
      FUpdating := False;
    end;
  end;
end;

procedure TQryRecInfo.Bind(const PropertyName: string; Component: TComponent; Direction: TBindDirection = bdTwoWay);
var
  BindingInfo: TComponentBindingInfo;
  PropName: string;
begin
  if not Assigned(Component) then
    Exit;

  // Determine the appropriate component property to bind to
  PropName := '';
  if Component is TEdit then
    PropName := 'Text'
  else if Component is TLabel then
    PropName := 'Caption'
  else if Component is TDateTimePicker then
    PropName := 'Date'
  else if Component is TMemo then
    PropName := 'Text'
  else if Component is TComboBox then
    PropName := 'Text'
  else if Component is TCheckBox then
    PropName := 'Checked'
  else if Component is TRadioButton then
    PropName := 'Checked'
  else if Component is TListBox then
    PropName := 'Items.Text'
  else if Component is TComboBoxEx then
    PropName := 'Text'
  // Add more component types as needed
  ;

  if PropName = '' then
    Exit;

  // Create binding info
  BindingInfo.Component := Component;
  BindingInfo.PropertyName := PropName;
  BindingInfo.Direction := Direction;

  // Remove any existing binding for this property
  Unbind(PropertyName);

  // Add the new binding
  FBindings.Add(PropertyName, BindingInfo);

  // Set up change notification
  if Component is TEdit then
    TEdit(Component).OnChange := ComponentChangeHandler
  else if Component is TMemo then
    TMemo(Component).OnChange := ComponentChangeHandler
  else if Component is TComboBox then
    TComboBox(Component).OnChange := ComponentChangeHandler
  else if Component is TDateTimePicker then
    TDateTimePicker(Component).OnChange := ComponentChangeHandler
  else if Component is TCheckBox then
    TCheckBox(Component).OnClick := ComponentChangeHandler
  else if Component is TRadioButton then
    TRadioButton(Component).OnClick := ComponentChangeHandler
  else if Component is TListBox then
    TListBox(Component).OnClick := ComponentChangeHandler
  // Add more component event handlers as needed
  ;

  // Initial update
  FBindings.UpdateComponents;
end;

procedure TQryRecInfo.BindByHint;
var
  Context: TRttiContext;
  RttiType: TRttiType;
  Prop: TRttiProperty;
  Owner: TComponent;
  I: Integer;
  Component: TComponent;
begin
  Context := TRttiContext.Create;
  try
    RttiType := Context.GetType(Self.ClassType);

    // Get the owner component (form or frame)
    // Assumes that the owner is the owner of all bound components
    Owner := nil;
    if FBindings.Count > 0 then
      Owner := FBindings.Items[FBindings.Count - 1].Component.Owner
    else
    begin
      // Attempt to find the owner from the component hints
      // This requires that at least one binding exists or handle differently
      for I := 0 to Screen.FormCount - 1 do
      begin
        if Screen.Forms[I] is TComponent then
        begin
          Owner := Screen.Forms[I];
          Break;
        end;
      end;
    end;

    if not Assigned(Owner) then
      Exit;

    // Look through all components
    for I := 0 to Owner.ComponentCount - 1 do
    begin
      Component := Owner.Components[I];
      if (Component is TControl) and (Component.Hint <> '') and Assigned(RttiType.GetProperty(Component.Hint)) then
        Bind(Component.Hint, Component);
    end;
  finally
    // No need to free TRttiContext as it's a record
  end;
end;

procedure TQryRecInfo.Unbind(const PropertyName: string);
var
  BindingInfo: TComponentBindingInfo;
begin
  if FBindings.TryGetValue(PropertyName, BindingInfo) then
  begin
    // Remove change handler
    if BindingInfo.Component is TEdit then
      TEdit(BindingInfo.Component).OnChange := nil
    else if BindingInfo.Component is TMemo then
      TMemo(BindingInfo.Component).OnChange := nil
    else if BindingInfo.Component is TComboBox then
      TComboBox(BindingInfo.Component).OnChange := nil
    else if BindingInfo.Component is TDateTimePicker then
      TDateTimePicker(BindingInfo.Component).OnChange := nil
    else if BindingInfo.Component is TCheckBox then
      TCheckBox(BindingInfo.Component).OnClick := nil
    else if BindingInfo.Component is TRadioButton then
      TRadioButton(BindingInfo.Component).OnClick := nil
    else if BindingInfo.Component is TListBox then
      TListBox(BindingInfo.Component).OnClick := nil
    // Remove more component event handlers as needed
    ;

    FBindings.Remove(PropertyName);
  end;
end;

procedure TQryRecInfo.Unbind(Component: TComponent);
var
  Pair: TPair<string, TComponentBindingInfo>;
  ToRemove: TList<string>;
  PropName: string;
begin
  ToRemove := TList<string>.Create;
  try
    for Pair in FBindings do
    begin
      if Pair.Value.Component = Component then
      begin
        ToRemove.Add(Pair.Key);
      end;
    end;

    for PropName in ToRemove do
      Unbind(PropName);
  finally
    ToRemove.Free;
  end;
end;

procedure TQryRecInfo.UnbindAll;
var
  PropNames: TArray<string>;
  PropName: string;
begin
  PropNames := FBindings.Keys.ToArray;
  for PropName in PropNames do
    Unbind(PropName);
end;

{ TQryRecArray<T> }

function TQryRecArray<T>.GetCount: Integer;
begin
  Result := FQryRecInfoList.Count;
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;

end.