Min jopera software tan-negozju li kibret f‘ Delphi jafu l-qasam ta‘ tensjoni: minn naħa wieħed trid oġġetti ta‘ dominju strutturati u saffijiet ċari, minn naħa oħra hemm Datasets, Variants, importazzjonijiet CSV, payloads ta‘ interfeys jew API REST li jridu jinżammu mapped fuq oġġetti b’xi mod. Hawn tibda l-bżonn għall- Delphi RTTI für Mapping ohne Magie: mapping permezz ta‘ Reflection (RTTI = Run-Time Type Information, informazzjoni tat-tip waqt il-ħin tal-eżekuzzjoni), iżda b’mod li jibqa‘ trasparenti, faċli biex tdebugġa u mhux moħbi fuq konvenzjonijiet jew manipulazzjonijiet tal-ismijiet.
Il-punt ewlieni: il-„maġija“ normalment ma toħroġx minn RTTI per se, iżda minn regoli implisi. Meta r-regoli tal-mapping jkunu espliċiti f’attributi, il-konverżjonijiet ikunu ċentralizzati u l-iżbalji jindikaw kawża ċara, RTTI jsir għodda minflok sorpriża.
Għaliex it-RTTI-Mapping f‘ Delphi spiss jaqa‘
It-mapping ibbażat fuq RTTI fl-iskejjel reali rari jfalli minħabba l-idea innifisha, imma spiss minħabba kundizzjonijiet limiti:
- Formati ta‘ dejta legacy: Null/Empty/0 mhumiex separati b’mod nadif, it-tipi tal-kamp jinbidlu, strings fihom „N/A“.
- Konvenzjonijiet li jmorru b’mod gradwali: „Il-kamp jissejjaħ bħall-Property“ jaħdem sa l-ewwel alias, join jew isem tal-Property rifattorizzat.
- Diffiċli biex tdebugggja: Jekk mapper „semplicement ma jissettjax xejn“, mbagħad jonqos is-sors. Fil-operazzjoni dan ikun perikoluż.
- Mitoloġiji dwar il-prestazzjoni: RTTI spiss jiġi stmat b’mod ġenerali bħala „bil-mod“, filwaqt li spiss in-nuqqas ta‘ caching hu l-problema.
Għalhekk approċċ sostenibbli għandu (1) metadati tal-mapping espliċiti, (2) jindirizza b’mod ċar il-konverżjoni u s-semantika tal-null, (3) jipprovdi rapporti ta‘ żbalji u output ta‘ debug, u (4) jaħżen l-informazzjoni RTTI fil-cache.
Delphi RTTI für Mapping ohne Magie: Prinċipji tad-disinn
Il-mudell hawn taħt hu intenzjonalment sempliċi fil-aħjar sens: ir-regoli huma viżibbli, l-effetti sekondarji huma limitati, u jista‘ jiġi integrat gradwalment fil-moduli eżistenti.
- Attributi minflok konvenzjoni ta‘ isim: Property tingħata attribut li jsemmilha l-kolonna tas-sors.
- Opt-in: Biss il-Properties immarkati jiġu msetta. L-ebda sorpriżi minn „il-proprjetajiet kollha ppubblikati“.
- Konverżjoni f’post wieħed: Variant/String/Integer/Boolean/Enum/Nullable jiġu mappjati b’mod ċentrali.
- Modalità Debug: Skond il-bżonn, jinżamm protokoll dwar liema kampjiet ġew imsetta jew injorati – u r-raġuni.
- RTTI-Caching: L-iktar partijiet kostużi (lista tal-Property, valutazzjoni tal-attributi) jiġu ppreparati għal kull tip.
Snippett tal-kodiċi: Mapping bl-Attributi, RTTI, Caching u Debug
Is-snippett jimappja ringiela (per eżempju minn BDE-Ablosung mit nativer Anbindung via TDataSet) fuq oġġett. Minflok ma nkunu koppjati b’mod fissi mad-TField, nużaw interfazzja żgħira ta‘ reader. Dan huwa siewi fil-prattika għax tista‘ tuża l-istess loġika aktar tard għal JSON, INI, CSV jew risposti tal-API.
unit RttiMapping;
interface
uses
System.SysUtils, System.Rtti, System.TypInfo, System.Generics.Collections,
System.Variants;
type
// Mappar esplicit: Property <- isem tas-sors
MapFromAttribute = class(TCustomAttribute)
private
FName: string;
public
constructor Create(const AName: string);
property Name: string read FName;
end;
// Astrazzjoni żgħira: tipprovdi valur + iddifferenzja eżistenza/NULL
IValueReader = interface
['{7D1E5864-7D3A-4D30-BD1C-0A94F7E6C0EF}']
function HasValue(const AName: string): Boolean;
function IsNull(const AName: string): Boolean;
function GetValue(const AName: string): Variant;
end;
TRttiMapOptions = set of (moDebug, moIgnoreMissing, moIgnoreNull);
ERttiMappingError = class(Exception);
TRttiMapper = class
private
type
TPropMap = record
Prop: TRttiProperty;
SourceName: string;
end;
TTypeCache = class
Props: TArray<TPropMap>;
end;
private
class var FCache: TObjectDictionary<PTypeInfo, TTypeCache>;
class var FCacheLock: TObject;
class function GetOrBuildCache(ATypeInfo: PTypeInfo): TTypeCache; static;
class function FindMapFromAttr(const AProp: TRttiProperty): string; static;
class procedure SetPropertyValue(const AInstance: TObject; const AProp: TRttiProperty;
const AValue: Variant); static;
class function VariantToBoolean(const V: Variant): Boolean; static;
class function VariantToEnumOrdinal(AEnumType: TRttiType; const V: Variant): Integer; static;
public
class constructor Create;
class destructor Destroy;
class procedure MapToObject(const AReader: IValueReader; const ATarget: TObject;
const AOptions: TRttiMapOptions = [moIgnoreMissing]); static;
end;
implementation
{ MapFromAttribute }
constructor MapFromAttribute.Create(const AName: string);
begin
inherited Create;
FName := AName;
end;
{ TRttiMapper }
class constructor TRttiMapper.Create;
begin
FCache := TObjectDictionary<PTypeInfo, TTypeCache>.Create([doOwnsValues]);
FCacheLock := TObject.Create;
end;
class destructor TRttiMapper.Destroy;
begin
FCache.Free;
FCacheLock.Free;
end;
class function TRttiMapper.FindMapFromAttr(const AProp: TRttiProperty): string;
var
Attr: TCustomAttribute;
begin
Result := '';
for Attr in AProp.GetAttributes do
if Attr is MapFromAttribute then
Exit(MapFromAttribute(Attr).Name);
end;
class function TRttiMapper.GetOrBuildCache(ATypeInfo: PTypeInfo): TTypeCache;
var
Ctx: TRttiContext;
RType: TRttiType;
P: TRttiProperty;
L: TList<TPropMap>;
Src: string;
M: TPropMap;
begin
TMonitor.Enter(FCacheLock);
try
if FCache.TryGetValue(ATypeInfo, Result) then
Exit;
Result := TTypeCache.Create;
Ctx := TRttiContext.Create;
RType := Ctx.GetType(ATypeInfo);
L := TList<TPropMap>.Create;
try
for P in RType.GetProperties do
begin
if not P.IsWritable then
Continue;
// Opt-in: biss Properties b'Attribbut
Src := FindMapFromAttr(P);
if Src = '' then
Continue;
M.Prop := P;
M.SourceName := Src;
L.Add(M);
end;
Result.Props := L.ToArray;
finally
L.Free;
end;
FCache.Add(ATypeInfo, Result);
finally
TMonitor.Exit(FCacheLock);
end;
end;
class function TRttiMapper.VariantToBoolean(const V: Variant): Boolean;
var
S: string;
begin
if VarIsBool(V) then
Exit(V);
if VarIsNumeric(V) then
Exit(V <> 0);
S := Trim(VarToStr(V)).ToLower;
if (S = '1') or (S = 'true') or (S = 't') or (S = 'y') or (S = 'yes') then
Exit(True);
if (S = '0') or (S = 'false') or (S = 'f') or (S = 'n') or (S = 'no') then
Exit(False);
raise ERttiMappingError.CreateFmt('Konverżjoni għal Boolean falliet: "%s"', [VarToStr(V)]);
end;
class function TRttiMapper.VariantToEnumOrdinal(AEnumType: TRttiType; const V: Variant): Integer;
var
Ord: Integer;
Name: string;
begin
if VarIsNumeric(V) then
begin
Ord := Integer(V);
if (Ord < GetTypeData(AEnumType.Handle)^.MinValue) or
(Ord > GetTypeData(AEnumType.Handle)^.MaxValue) then
raise ERttiMappingError.CreateFmt('Ordinal tal-enum barra mill-firxa: %d', [Ord]);
Exit(Ord);
end;
Name := VarToStr(V);
Ord := GetEnumValue(AEnumType.Handle, Name);
if Ord < 0 then
raise ERttiMappingError.CreateFmt('Isem tal-enum mhux magħruf: "%s"', [Name]);
Result := Ord;
end;
class procedure TRttiMapper.SetPropertyValue(const AInstance: TObject;
const AProp: TRttiProperty; const AValue: Variant);
var
V: TValue;
T: TRttiType;
Ord: Integer;
begin
T := AProp.PropertyType;
// Konverżjoni intenzjonalment selettiva: aħjar li tfalli b'mod ċar milli tikkonverti xi ħaġa b'mod mhux determinat.
case T.TypeKind of
tkUString, tkString, tkLString, tkWString:
V := TValue.From<string>(VarToStr(AValue));
tkInteger, tkInt64:
V := TValue.From<Int64>(VarAsType(AValue, varInt64));
tkFloat:
V := TValue.From<Double>(VarAsType(AValue, varDouble));
tkEnumeration:
begin
if T.Handle = TypeInfo(Boolean) then
V := TValue.From<Boolean>(VariantToBoolean(AValue))
else
begin
Ord := VariantToEnumOrdinal(T, AValue);
V := TValue.FromOrdinal(T.Handle, Ord);
end;
end;
tkSet:
raise ERttiMappingError.CreateFmt('Set-Mapping mhux implementat għal %s', [AProp.Name]);
tkClass:
raise ERttiMappingError.CreateFmt('Class-Property Mapping mhux implementat għal %s', [AProp.Name]);
else
raise ERttiMappingError.CreateFmt('TypeKind mhux appoġġjat (%s) għal %s',
[GetEnumName(TypeInfo(TTypeKind), Ord(T.TypeKind)), AProp.Name]);
end;
AProp.SetValue(AInstance, V);
end;
class procedure TRttiMapper.MapToObject(const AReader: IValueReader;
const ATarget: TObject; const AOptions: TRttiMapOptions);
var
Cache: TTypeCache;
M: TPropMap;
V: Variant;
Msg: string;
begin
if (ATarget = nil) or (AReader = nil) then
raise ERttiMappingError.Create('MapToObject: Reader jew Target huwa nil');
Cache := GetOrBuildCache(ATarget.ClassInfo);
for M in Cache.Props do
begin
if not AReader.HasValue(M.SourceName) then
begin
if not (moIgnoreMissing in AOptions) then
raise ERttiMappingError.CreateFmt('Sors nieqes: "%s" għal Property %s',
[M.SourceName, M.Prop.Name]);
Continue;
end;
if AReader.IsNull(M.SourceName) then
begin
if moIgnoreNull in AOptions then
Continue;
// Mingħajr mekanika Nullable/Optional, ma jistax jitqiegħed NULL b'mod sensat.
Continue;
end;
V := AReader.GetValue(M.SourceName);
try
SetPropertyValue(ATarget, M.Prop, V);
if moDebug in AOptions then
begin
Msg := Format('Mapped %s <- %s (%s)', [M.Prop.Name, M.SourceName, VarTypeAsText(VarType(V))]);
OutputDebugString(PChar(Msg));
end;
except
on E: Exception do
raise ERttiMappingError.CreateFmt('Żball tal-mapping għal %s <- %s: %s',
[M.Prop.Name, M.SourceName, E.Message]);
end;
end;
end;
end.
Għalxiex dan iservi
Tikseb mapping li jista‘ jiġi vvalutat b’mod ċar fil-Code-Reviews:
- Kull Property mappjata hija mmarkata viżwalment (atribut).
- Il-konverżjoni hija ċentrali, u b’hekk konsistenti u ttestjabbli.
- Il-messaġġi ta‘ żball jindikaw liema Property u liema sors huma affettwati.
- Modalità debug tipprovdulek, fil-każ ta‘ dubju, il-katina ta‘ evidenza mingħajr ma jkollok bżonn breakpoints fil-proċess ta‘ produzzjoni.
Kundizzjonijiet limitu u ostakli tipici
- NULL-Semantik: Mingħajr kunċett proprju ta‘ Nullable (eż.
Nullable<T>jew Option-Types) l-issettjar ta‘ NULL mhuwiex uniku. Fil-snippet NULL tiġi skippjata b’mod default. Dan hu konservattiv u jipprevjeni sovrascrizzjonijiet silentjużi. - TRttiContext-Lebensdauer: Nabnu l-cache darba għal kull tip u nħassru l-Context wara. Dan hu prassi komuni. Importanti: Tħallux tkun qed toħloq RT TI-Context ġdid għal kull assenja ta‘ field.
- Threading: Il-cache huwa protett permezz ta‘ Monitor. F’mappings b’paralleli għoljin (eż. REST-Server) għandek tivverifika wkoll jekk tagħmel ‚warm‘ il-cache waqt l-avvio (Preload), biex tnaqqas il-lock-contention.
- PropertyType Kind:
tkClassutkSethuma intenzjonalment mhux implementati. Għal oġġetti inkapsulati għandek tmapja rekurżivament (bi policy ċara) jew tassenja b’mod manwali u intenzjonat. - Locale-Fallen:
varDoublepermezz ta‘VarAsTypehuwa relattivament robust, imma strings bħal „1,23“ vs. „1.23“ għadhom problema. Jekk is-sorsi tiegħek jirritornaw strings, parser proprju (bi Culture definit) spiss ikun aħjar.
Varjant għal FireDAC u TDataSet: Reader-Adapter minflok koppja mal-Mapper
F’BDE-Ablosung mit nativer Anbindung jew f’applikazzjonijiet klassiċi VCL/Win32 is-sors spiss huwa TDataSet. Minflok tgħaqqad il-Mapper ma‘ TField, ikteb adapter li jimplimenta l-interface IValueReader. Il-vantaġġ: il-Mapper jibqa‘ indipendenti mill-aċċess tad-dejta (importanti meta tirtira l-aċċess tad-dejta aktar tard f’services jew f‘REST-Server).
uses Data.DB, System.Variants, RttiMapping;
type
TDataSetValueReader = class(TInterfacedObject, IValueReader)
private
FDS: TDataSet;
public
constructor Create(ADS: TDataSet);
function HasValue(const AName: string): Boolean;
function IsNull(const AName: string): Boolean;
function GetValue(const AName: string): Variant;
end;
constructor TDataSetValueReader.Create(ADS: TDataSet);
begin
inherited Create;
FDS := ADS;
end;
function TDataSetValueReader.HasValue(const AName: string): Boolean;
begin
Result := (FDS <> nil) and (FDS.FindField(AName) <> nil);
end;
function TDataSetValueReader.IsNull(const AName: string): Boolean;
var
F: TField;
begin
F := FDS.FindField(AName);
Result := (F = nil) or F.IsNull;
end;
function TDataSetValueReader.GetValue(const AName: string): Variant;
begin
Result := FDS.FieldByName(AName).Value;
end;
B’dan il-mod mapping konkreta tidher hekk:
type
TOrderRow = class
private
FId: Int64;
FCustomerNo: string;
FIsClosed: Boolean;
public
[MapFrom('order_id')]
property Id: Int64 read FId write FId;
[MapFrom('customer_no')]
property CustomerNo: string read FCustomerNo write FCustomerNo;
[MapFrom('is_closed')]
property IsClosed: Boolean read FIsClosed write FIsClosed;
end;
// ...
var
Row: TOrderRow;
Reader: IValueReader;
begin
Row := TOrderRow.Create;
try
Reader := TDataSetValueReader.Create(MyQuery);
TRttiMapper.MapToObject(Reader, Row, [moIgnoreMissing, moDebug, moIgnoreNull]);
finally
Row.Free;
end;
end;
Fejn dan l-approċċ jiswa — u fejn le
Dan il-mudell ġeneralment jiswa fi tliet sitwazzjonijiet:
- Modernizzazzjoni gradwali: Tixtiequ tinroduċu oġġetti tad-dominju mingħajr ma tbiddlu kompletament l-aċċess tad-dejta minnufih (tipikament fil-Delphi Modernizzazzjoni f’applikazzjonijiet eżistenti).
- Punti tal-interface: Importazzjonijiet CSV/Excel, REST-Payloads jew sorsi ta‘ dejta „miksti“ jeħtieġu konverżjoni robusta u messaġġi ta‘ żball ċari.
- Mantenibbiltà fit-tim: L-attributi jagħmlu r-regoli tal-mapping viżibbli u reviżjabbli, li għandu valur kbir f’kodiċi kbar.
Hemm ukoll limiti ċari tal-użu:
- Grafi ta‘ oġġetti kumplessi (Child-Collections, referenzi ċikliċi) m’għandhomx jiġu mappjati b’mod „automagiku“. Hawnhekk kodiċi esplicitu jew mudell Assembler/Factory separati huwa spiss aktar stabbli.
- Hotpaths ta‘ throughput għoli (pereżempju ETL ta‘ dejta massiva) jiffavorixxu mapperi ġenerati mill-kodiċi jew mapping ottimizzat b’mod manwali, anki jekk RTTI tkun cached.
- Nullable/Optional huwa suġġett separat. Jekk verament għandek bżonn tiddistingwi bejn „mhux preżenti“, „NULL“ u „Default“, għandek tesprimi dan fil-mudell tad-dominju, mhux taħbih fil-mapper.
Pożizzjonament fl-arkitettura u fl-operat
Minn perspettiva arkitettonika, dan il-mapper huwa komponent ta‘ infrastruttura fil-fruntiera bejn ir-repreżentazzjoni tad-dejta u d-dominju. Mhuwiex sostitut ta‘ stratifikazzjoni nadifa, iżda jista‘ jippermettiha: l-aċċess tad-dejta (FireDAC, SQL, Views) jista‘ jibqa‘ pragmatiku, filwaqt li d-dominju jibqa‘ konsistenti. Fis-sistemi b’ħafna saffar (spiss imsejħa Layer-3 Arkitettura: UI, Domain/Services, Infrastruktur) il-mapper għandu jkun fin-infrastruttura u jintuża mis-servizzi, mhux mill-formoli tal-UI.
Operattivament importanti: Ma tħallix moDebug attivat b’mod permanenti fis-servizzi produttivi; uża dan b’mod mirqum. Għal problemi tad-dejta diffiċli biex jirreprodukku, huwa sensat li jkollok triq dijanjostika li tista‘ tinbidel (konfigurazzjoni, Feature-Flag). Inkella hemm riskju ta‘ volum kbir ta‘ log u effetti sekondarji.
Konklużjoni: RTTI iva, iżda biss bi linji gwida ċari
Delphi RTTI għal Mapping mingħajr maġija jaħdem tajjeb meta tuża RTTI bħala għodda għal metadati deklarattivi — mhux bħala sejħa għal euristiċi moħbija. Attribwiti bħala opt-in, konverżjoni ċentralizzata, cache għal kull tip u testi ta‘ żball ċari jġibu s-suġġett minn „mhux trasparenti“ għal „operabbli“. L-approċċ hu b’mod konxju mhux universali: għal grafi imwaħħla, semantika stretta tal-null jew prestazzjoni massima għandek bżonn komponenti addizzjonali. Bħala pont robust bejn Dataset/Legacy-Strukturen u oġġetti ta‘ dominju aktar moderni, huwa f’ħafna Delphi-codebasen eżatt il-pass pragmatiku li jagħmel il-modernizzazzjoni possibbli.
Jekk f’applikazzjoni Delphi li kibret issa qed tiffaċċja limiti tal-mapping, problemi fil-kwalità tad-dejta jew modernizzazzjoni gradwali, nistgħu nissettjaw dan flimkien b’mod nadif u nintegrawh fl-arkitettura tiegħek: Ikkuntattjana.
Fil-kuntest professjonali, Delphi Rtti Mapping u Attribute Mapping Delphi jilagħbu rwol importanti meta l-integrazzjonijiet, il-flussi tad-dejta u l-iżvilupp kontinwu jridu jaħdmu flimkien b’mod nadif.
Niddiskutu proġett jew inizjattiva ta‘ modernizzazzjoni ma‘ Net-Base.