Reputation: 502
I'm working with Serialization/Deserialization using NEON library from Paolo Rossi.
i'm trying to populate this class using RTTI, with data that i get from a database. The properties on the class have the same name of the fields in the database.
In the library, i have this Nullable Record :
unit Neon.Core.Nullables;
interface
uses
System.SysUtils, System.Variants, System.Classes, System.Generics.Defaults, System.Rtti,
System.TypInfo, System.JSON;
type
ENullableException = class(Exception);
{$RTTI EXPLICIT FIELDS([vcPrivate]) METHODS([vcPrivate])}
Nullable<T> = record
private
FValue: T;
FHasValue: string;
procedure Clear;
function GetValueType: PTypeInfo;
function GetValue: T;
procedure SetValue(const AValue: T);
function GetHasValue: Boolean;
public
constructor Create(const Value: T); overload;
constructor Create(const Value: Variant); overload;
function Equals(const Value: Nullable<T>): Boolean;
function GetValueOrDefault: T; overload;
function GetValueOrDefault(const Default: T): T; overload;
property HasValue: Boolean read GetHasValue;
function IsNull: Boolean;
property Value: T read GetValue;
class operator Implicit(const Value: Nullable<T>): T;
class operator Implicit(const Value: Nullable<T>): Variant;
class operator Implicit(const Value: Pointer): Nullable<T>;
class operator Implicit(const Value: T): Nullable<T>;
class operator Implicit(const Value: Variant): Nullable<T>;
class operator Equal(const Left, Right: Nullable<T>): Boolean;
class operator NotEqual(const Left, Right: Nullable<T>): Boolean;
end;
NullString = Nullable<string>;
NullBoolean = Nullable<Boolean>;
NullInteger = Nullable<Integer>;
NullInt64 = Nullable<Int64>;
NullDouble = Nullable<Double>;
NullDateTime = Nullable<TDateTime>;
implementation
uses
Neon.Core.Utils;
{ Nullable<T> }
constructor Nullable<T>.Create(const Value: T);
var
a: TValue;
begin
FValue := Value;
FHasValue := DefaultTrueBoolStr;
end;
constructor Nullable<T>.Create(const Value: Variant);
begin
if not VarIsNull(Value) and not VarIsEmpty(Value) then
Create(TValue.FromVariant(Value).AsType<T>)
else
Clear;
end;
procedure Nullable<T>.Clear;
begin
FValue := Default(T);
FHasValue := '';
end;
function Nullable<T>.Equals(const Value: Nullable<T>): Boolean;
begin
if HasValue and Value.HasValue then
Result := TEqualityComparer<T>.Default.Equals(Self.Value, Value.Value)
else
Result := HasValue = Value.HasValue;
end;
function Nullable<T>.GetHasValue: Boolean;
begin
Result := FHasValue <> '';
end;
function Nullable<T>.GetValueType: PTypeInfo;
begin
Result := TypeInfo(T);
end;
function Nullable<T>.GetValue: T;
begin
if not HasValue then
raise ENullableException.Create('Nullable type has no value');
Result := FValue;
end;
function Nullable<T>.GetValueOrDefault(const Default: T): T;
begin
if HasValue then
Result := FValue
else
Result := Default;
end;
function Nullable<T>.GetValueOrDefault: T;
begin
Result := GetValueOrDefault(Default(T));
end;
class operator Nullable<T>.Implicit(const Value: Nullable<T>): T;
begin
Result := Value.Value;
end;
class operator Nullable<T>.Implicit(const Value: Nullable<T>): Variant;
begin
if Value.HasValue then
Result := TValue.From<T>(Value.Value).AsVariant
else
Result := Null;
end;
class operator Nullable<T>.Implicit(const Value: Pointer): Nullable<T>;
begin
if Value = nil then
Result.Clear
else
Result := Nullable<T>.Create(T(Value^));
end;
class operator Nullable<T>.Implicit(const Value: T): Nullable<T>;
begin
Result := Nullable<T>.Create(Value);
end;
class operator Nullable<T>.Implicit(const Value: Variant): Nullable<T>;
begin
Result := Nullable<T>.Create(Value);
end;
function Nullable<T>.IsNull: Boolean;
begin
Result := FHasValue = '';
end;
class operator Nullable<T>.Equal(const Left, Right: Nullable<T>): Boolean;
begin
Result := Left.Equals(Right);
end;
class operator Nullable<T>.NotEqual(const Left, Right: Nullable<T>): Boolean;
begin
Result := not Left.Equals(Right);
end;
procedure Nullable<T>.SetValue(const AValue: T);
begin
FValue := AValue;
FHasValue := DefaultTrueBoolStr;
end;
end.
Here's the model class :
type
TMyClass = class(TPersistent)
private
FMyIntegerProp: Nullable<Integer>;
procedure SetMyIntegerProp(const Value: Nullable<Integer>);
published
Property MyIntegerProp: Nullable<Integer> read FMyIntegerProp write SetMyIntegerProp;
end;
implementation
{ TMyClass }
procedure TMyClass.SetMyIntegerProp(const Value: Nullable<Integer>);
begin
FMyIntegerProp := Value;
end;
And my code so far :
procedure DatasetToObject(AObject: TObject; AQuery: TFDQuery);
var
n: Integer;
LRttiContext: TRttiContext;
LRttiType: TRttiType;
LRttiProperty: TRttiProperty;
LFieldName: string;
Value: TValue;
LValue: TValue;
LRttiMethod : TRttiMethod;
begin
LRttiContext := TRttiContext.Create;
try
LRttiType := LRttiContext.GetType(AObject.ClassType);
for n := 0 to AQuery.FieldCount - 1 do
begin
LRttiProperty := LRttiType.GetProperty(AQuery.Fields[n].FieldName);
if (LRttiProperty <> nil) and (LRttiProperty.PropertyType.TypeKind = tkRecord) then
begin
LValue := LRttiProperty.GetValue(AObject);
LRttiMethod := LRttiContext.GetType(LValue.TypeInfo).GetMethod('SetValue');
if (LRttiProperty.PropertyType.Name = 'Nullable<System.Integer>') then
LRttiMethod.Invoke(LValue, [AQuery.Fields[n].AsInteger]).AsInteger;
end;
end;
finally
LRttiContext.Free;
end;
end;
but no success so far, any help will be appreciated.
Upvotes: 1
Views: 934
Reputation: 595961
Nullable.SetValue()
does not have a return value, but you are trying to read one when you call AsInteger
on the TValue
that TRttiMethod.Invoke()
returns. That will cause an exception to be raised at runtime.
Also, when you read the value of the TMyClass.MyIntegerProp
property, you will end up with a copy of its Nullable
record, so Invoke()
'ing SetValue()
on that copy is not going to update the MyIntegerProp
property. You will have to assign the modified Nullable
back to MyIntegerProp
afterwards, eg:
LValue := LRttiProperty.GetValue(AObject);
LRttiMethod := LRttiContext.GetType(LValue.TypeInfo).GetMethod('SetValue');
LRttiMethod.Invoke(LValue, [AQuery.Fields[n].AsInteger]);
LRttiProperty.SetValue(AObject, LValue); // <-- add this!
That being said, the Nullable.Value
property is read-only, but Nullable
has a SetValue()
method, so I would suggest changing the Value
property to be read-write instead, eg:
property Value: T read GetValue write SetValue;
Then you can set the Value
property via RTTI instead of Invoke()
'ing the SetValue()
method directly:
var
...
//LRttiMethod: TRttiMethod;
LRttiValueProp: TRttiProperty;
...
...
LRttiProperty := LRttiType.GetProperty(AQuery.Fields[n].FieldName);
if (LRttiProperty <> nil) and
(LRttiProperty.PropertyType.TypeKind = tkRecord) and
(LRttiProperty.PropertyType.Name = 'Nullable<System.Integer>') then
begin
LValue := LRttiProperty.GetValue(AObject);
{
LRttiMethod := LRttiContext.GetType(LValue.TypeInfo).GetMethod('SetValue');
LRttiMethod.Invoke(LValue, [AQuery.Fields[n].AsInteger]);
}
LRttiValueProp := LRttiContext.GetType(LValue.TypeInfo).GetProperty('Value');
LRttiValueProp.SetValue(LValue.GetReferenceToRawData, AQuery.Fields[n].AsInteger);
LRttiProperty.SetValue(AObject, LValue);
end;
Upvotes: 3