turns-00037.parquet:42336
c75cb5cfa75613b2a752c4c4
turn 1/2gpt-4o-2024-08-06EnglishBrazil3473 words
degenerate_repetitionAbsentFinal dense release
USER
unit interposer_class;
interface
uses
Vcl.Forms, Vcl.Controls, CommCtrl, Messages, sPanel, sEdit, sPageControl,
System.Classes, AdvCGrid, Uni, pixel_classes, ORM_utils, SysUtils, Dialogs,
System.Generics.Defaults, Generics.Collections, System.Hash, System.Rtti,
Vcl.StdCtrls, Vcl.WinXCtrls, Vcl.WinXPickers;
{$TYPEINFO ON}
type
/// <summary>
/// Represents the key used in the component-ORM mapping dictionary.
/// Combines a reference to an ORM record with a specific field name.
/// </summary>
TORMKey = record
ORMRec: TQryRecInfo;
FieldName: string;
end;
/// <summary>
/// Custom comparer for TORMKey to handle equality checks and hashing.
/// </summary>
TORMKeyComparer = class(TEqualityComparer<TORMKey>)
function Equals(const Left, Right: TORMKey): Boolean; override;
function GetHashCode(const Key: TORMKey): Integer; override;
end;
/// <summary>
/// Manages the binding between UI components and ORM records.
/// </summary>
TORMManager = class
private
FComponentDict: TDictionary<TORMKey, TComponent>;
public
constructor Create;
destructor Destroy; override;
/// <summary>
/// Registers a component with its corresponding ORM record and field name.
/// </summary>
procedure RegisterComponent(Component: TComponent; ORMRec: TQryRecInfo; const FieldName: string);
/// <summary>
/// Unregisters a component from ORM binding.
/// </summary>
procedure UnregisterComponent(Component: TComponent);
/// <summary>
/// Finds the component associated with a specific ORM record and field name.
/// </summary>
function FindComponent(ORMRec: TQryRecInfo; const FieldName: string): TComponent;
/// <summary>
/// Retrieves all registered components.
/// </summary>
function GetAllComponents: TArray<TComponent>;
/// <summary>
/// Updates a component's UI based on the latest ORM record data.
/// </summary>
procedure UpdateComponentFromORM(Component: TComponent; ORMRec: TQryRecInfo; const FieldName: string);
/// <summary>
/// Retrieves the ORM record associated with a given component.
/// </summary>
function GetORMRecForComponent(Component: TComponent): TQryRecInfo;
/// <summary>
/// Retrieves the field name associated with a given component.
/// </summary>
function GetFieldNameForComponent(Component: TComponent): string;
// New method added to register components based on hints
procedure RegisterComponentsByHint(MyForm: TForm; ORMRec: TQryRecInfo);
end;
/// <summary>
/// Helper methods extending TComponent to interface with ORMManager.
/// </summary>
TComponentHelper = class helper for TComponent
public
/// <summary>
/// Retrieves the ORM record associated with the component.
/// </summary>
function GetORMRec: TQryRecInfo;
/// <summary>
/// Retrieves the field name associated with the component.
/// </summary>
function GetORMRecField: string;
end;
// Declare ORMManager as a global variable in the interface for accessibility
var
ORMManager: TORMManager;
implementation
uses
pixel_utils, System.Math, System.Types, Windows, TypInfo,
Data.DB, Vcl.Graphics;
// =============================
// TORMKeyComparer Implementation
// =============================
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;
// =============================
// TORMManager Implementation
// =============================
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
// To safely remove components, first copy the keys
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;
procedure TORMManager.UpdateComponentFromORM(Component: TComponent; ORMRec: TQryRecInfo; const FieldName: string);
var
FormattedText: string;
DateValue: TDateTime;
begin
FormattedText := ORMRec.GetFormattedText(FieldName);
if Component is TEdit then
TEdit(Component).Text := FormattedText
else if Component is TLabel then
TLabel(Component).Caption := FormattedText
else if Component is TDatePicker then
begin
if TryStrToDate(FormattedText, DateValue) then
TDatePicker(Component).Date := DateValue
else
begin
// Handle invalid date appropriately
ShowMessage(Format('Invalid date format for component: %s', [Component.Name]));
// Optionally, set to a default date, e.g., current date
// TDatePicker(Component).Date := Date;
end;
end
else if Component is TComboBox then
TComboBox(Component).Text := FormattedText
// Add more component types as needed...
else
ShowMessage('Unsupported component type: ' + Component.ClassName);
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;
// =============================
// TComponentHelper Implementation
// =============================
function TComponentHelper.GetORMRec: TQryRecInfo;
begin
Result := ORMManager.GetORMRecForComponent(Self);
end;
function TComponentHelper.GetORMRecField: string;
begin
Result := ORMManager.GetFieldNameForComponent(Self);
end;
// =============================
// Initialization and Finalization
// =============================
initialization
ORMManager := TORMManager.Create;
finalization
ORMManager.Free;
end.
------------------------
unit orm_utils;
interface
uses
System.SysUtils,
System.Classes,
System.Generics.Collections,
System.Rtti,
System.TypInfo,
Data.DB,
Uni,
pixel_classes,
orm_customattributes;
type
TQryRecInfo = class;
// Define the event type
TPropertyChangedEvent = procedure(Sender: TQryRecInfo; const PropertyName: string) of object;
EORMUtilsException = class(Exception);
/// <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>;
FOnPropertyChanged: TPropertyChangedEvent; // New event
procedure SetInfo(Index: Integer; const Value: TValue);
public
constructor Create;
destructor Destroy; override;
// Event property
property OnPropertyChanged: TPropertyChangedEvent read FOnPropertyChanged write FOnPropertyChanged;
// 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);
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;
// Example descendant of TQryRecInfo (You can create your own)
TDynRec = class(TQryRecInfo)
// Define additional properties as needed
end;
implementation
uses
System.Variants,
StrUtils,
pixel_utils; // Ensure this unit is in your project and properly implemented
{ TQryRecInfo }
constructor TQryRecInfo.Create;
begin
inherited Create;
FDirtyFields := TDictionary<string, Boolean>.Create;
FPreviousValues := TDictionary<string, TValue>.Create;
end;
destructor TQryRecInfo.Destroy;
begin
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;
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;
// 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
if Prop.PropertyType.Name.StartsWith('TNullable<') then
begin
// Use RTTI to determine the generic type parameter
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
// 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
raise EORMUtilsException.CreateFmt('Unsupported enumeration type: %s', [Prop.PropertyType.Name]);
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
// Retrieve property name and kind based on index
// Assuming that the index corresponds to the property order
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 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);
// Trigger the OnPropertyChanged event
if Assigned(FOnPropertyChanged) then
FOnPropertyChanged(Self, PropName);
finally
// No need to free TRttiContext as it's a record
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;
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; // 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
if Prop.PropertyType.Name.StartsWith('TNullable<') then
begin
// Use RTTI to determine the generic type parameter
if Field.IsNull then
ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), TValue.From<TNullable<string>>(NullValue))
else
begin
// Determine the generic type and set the value accordingly
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
// Handle non-nullable types
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 if Prop.PropertyType.Handle = TypeInfo(TDateTime) then
ObjType.GetField(ORMFieldName).SetValue(Pointer(RecInstance), Field.AsDateTime)
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
// 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;
end.
-------------------------
Review all TORMManager related code from interposer_class and refactor it into orm_utils, while keeping the orm_utils funtionalitiesASSISTANT
To reorganize the code by moving all `TORMManager` related functionalities from `interposer_class` into `orm_utils` while keeping `orm_utils` functionalities intact, follow these steps:
**Step-by-step Refactoring:**
1. **Extract `TORMManager` from `interposer_class`:**
Move the entire definition and implementation of `TORMManager` from `interposer_class` to `orm_utils`. Make sure to keep references to `TORMKey`, `TORMKeyComparer`, and any other required types.
2. **Update Interface and Implementation:**
Ensure that both interface and implementation sections in `orm_utils` are updated with the moved classes and methods.
3. **Adjust Uses Clauses:**
Modify the `uses` sections appropriately in both units (`interposer_class` and `orm_utils`). Ensure that any dependencies needed by `TORMManager` are also moved or referenced from `orm_utils`.
4. **Handle `initialization` and `finalization`:**
If the global `ORMManager` instance is being initialized and finalized, make sure such logic is correctly placed in `orm_utils`.
5. **Provide Interfaces and Helpers:**
If there are any helper classes or interfaces needed for `TORMManager` to function (like `TComponentHelper`), ensure they are correctly referenced or included in `orm_utils`.
6. **Test Functionality:**
After moving the code, test the functionality to ensure everything works correctly. Fix any errors or issues that arise from the changes.
Below is a conceptual illustration of the changes:
### Moving `TORMManager` to `orm_utils`
**orm_utils.pas:**
```pascal
unit orm_utils;
// ... existing uses clauses ...
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections, System.Generics.Defaults,
System.Rtti, Vcl.StdCtrls, Vcl.Controls, Vcl.Forms, Vcl.ComCtrls, Vcl.ExtCtrls;
// ... other type declarations ...
type
// Move TORMKey and TORMKeyComparer
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;
// Move TORMManager into this section
TORMManager = class
private
FComponentDict: TDictionary<TORMKey, TComponent>;
// ... other private methods and fields ...
public
constructor Create;
destructor Destroy; override;
// Public methods from TORMManager:
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;
var
ORMManager: TORMManager;
implementation
// ... implementation of methods ...
initialization
ORMManager := TORMManager.Create;
finalization
ORMManager.Free;
end.
```
**interposer_class.pas (Refactored):**
```pascal
unit interposer_class;
interface
uses
Vcl.Forms, Vcl.Controls, CommCtrl, Messages, System.Classes, orm_utils,
SysUtils, Dialogs, System.Generics.Defaults, Generics.Collections;
implementation
// interposer_class-specific implementation details...
end.
```
### Key Changes:
- **Move `TORMManager`, `TORMKey`, and `TORMKeyComparer` definitions and implementations to `orm_utils`.
- **Ensure that `TORMManager` methods and logic are correctly incorporated into `orm_utils`.
- **Ensure the refactoring does not disrupt existing ORM-related functionalities of `orm_utils`.
- **Remove any `TORMManager`-related logic from `interposer_class.pas` while keeping references to essential utilities and classes.
This refactoring centralizes ORM management responsibilities in `orm_utils`, simplifying maintenance and enhancing cohesion and focus within the modules.