Net-Base Iris

08.05.2026

Delphi RTTI do mhapáil gan draíochta: bunaithe ar airíonna, inrochtana le haghaidh debugála agus oiriúnach do shean-chórais

Patrún mapaíochta prágmatach le Delphi RTTI: airíonna seachas coinbhéiseanna, tiontuithe rialaithe, téacsanna earráide soiléire agus mód dífhabhtaithe a chabhraíonn i ndáiríre le linn oibriúcháin. Le slisníní cód foinse do mampa Dataset- nó Record‑go‑Réad gan draíocht fholaithe.

08.05.2026

An té a oibríonn bogearraí gnó atá fástha i Delphi a bhfuil eolas aige ar an réimse teanntachta: ar thaobh amháin tá riachtanas do réada réimse struchtúrtha agus sraitheanna soiléire, ach ar an taobh eile tá Datasets, Variants, CSV-ionsuithe, payloads comhéadan nó API REST a chaithfidh, ar bhealach éigin, a bheith meaitseáilte le réada. Is anseo a théann tú go tapa chuig Delphi RTTI do mheaitseáil gan draíocht: mapping trí Reflection (RTTI = Run-Time Type Information, faisnéis cineálacha ag am reatha), ach ar bhealach atá intuigthe, maith le debugáil agus nach mbraitheann go sonraí ar choinbhinsiúin ná ar chluichí ainmniúcháin.

An pointe lárnach: ní thagann an “draíocht” de ghnáth ón RTTI féin, ach ó rialacha implícite. Má tá rialacha meaitseála agus airíonna sainithe go soiléir, má bhíonn tiontuithe lárnaithe agus má léirítear earráidí le cúis shoiléir, éiríonn RTTI ina uirlis iontaofa seachas ina iontas.

Cén fáth a theipeann an RTTI-meaitseáil i Delphi go minic

Ní theipeann meaitseáil bhunaithe ar RTTI i gcórais réadacha de ghnáth ar an smaoineamh féin, ach ar choinníollacha imeallacha:

  • Foirmeacha sonraí legacy: Níl Null/Empty/0 scartha go soiléir, athraíonn cineálacha réimsí, agus bíonn „N/A“ i sraitheanna.
  • Coinbhinsiúin ag dul i bhfeidhm de réir a chéile: „Tá réimse le hainm cosúil leis an Property“ oibríonn go dtí go dtarlóidh an chéad alias, join nó athainmniú/athfhorbairt ainm an Property.
  • Deacair a debugáil: Má tá mapper ag „gan tada a chur“, bíonn sé dodhéanta an chúis a aimsiú ina dhiaidh sin. Sa chleachtas oibríochta tá sé sin dochreidte dona.
  • Miotais faoi fheidhmíocht: Cuireann daoine RTTI isteach go forleathan mar „mall“, cé go mbíonn easpa cacheála de ghnáth mar phríomhchúis an fhadhb.

Ba chóir go mbeadh cur chuige inbhuanaithe dá bhrí sin ina bhfuil (1) meiteashonraí meaitseála shonraí, (2) rialú ar tiontuithe agus ar shemantacht null, (3) tuairisciú earráidí agus aschur debug soiléir, agus (4) cacheáil eolas RTTI.

Delphi RTTI do mheaitseáil gan draíocht: Prionsabail dhearaidh

Tá an patrún seo intinneach mar gheall ar a shimplíocht: tá rialacha le feiceáil go soiléir, tá fo-iarmhairtí teoranta, agus is féidir é a tharraingt isteach go céimnitheach i modúil atá ann cheana.

  • Airíonna (Attribute) seachas coinbhinsiún ainm: Faigheann Property airíonna a ainmníonn an colún foinsí.
  • Opt-in: Ní leagfar ach na Properties marcáilte. Níl iontas ó „gach Property poiblí“.
  • Tiontú in aon áit amháin: Variant/String/Integer/Boolean/Enum/Nullable a mheaitseáil lárnach.
  • Modh Debug: Go roghnach, taifeadtar cé na réimsí a leagadh/nár leagadh — agus an chúis.
  • Cacheáil RTTI: Ullmhaítear na rudaí is costasaí (liosta na bProperty, meastóireacht airíonna) do gach cineál.

Slisnín cód: Attribut-mapping le RTTI, cacheáil agus debug

Cuireann an slisnín líne amháin (m.sh. ó BDE-athsholáthar le nasc dúchasach trí TDataSet) i láthair ar réad. In ionad an mapper a nascadh go daingean le TField, úsáidimid comhéadan léitheora beag. Tá sé seo luachmhar sa chleachtas toisc gur féidir an loighic chéanna a úsáid níos déanaí do JSON, INI, CSV nó freagraí API.

unit RttiMapping;

interface

uses
System.SysUtils, System.Rtti, System.TypInfo, System.Generics.Collections,
System.Variants;

type
// Mapaíocht shonrach: Property <- Ainm an fhoinse
MapFromAttribute = class(TCustomAttribute)
private
FName: string;
public
constructor Create(const AName: string);
property Name: string read FName;
end;

// Aicmiú beag: luach a sholáthar + idirdhealú idir láithreacht/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: Ach airíonna le tréith
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(‚Teip ar thiontú boolean: „%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(‚Enum-Ordinal lasmuigh den raon: %d‘, [Ord]);
Exit(Ord);
end;

Name := VarToStr(V);
Ord := GetEnumValue(AEnumType.Handle, Name);
if Ord < 0 then
raise ERttiMappingError.CreateFmt(‚Ainm enum anaithnid: „%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;

// Athrú roghnach go cúramach: is fearr teip shoiléir ná teip i bhfolach „ar bhealach éigin“.
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(‚Níl Set-mapping curtha i bhfeidhm do %s‘, [AProp.Name]);

tkClass:
raise ERttiMappingError.CreateFmt(‚Níl mappaíocht airíonna ranga curtha i bhfeidhm do %s‘, [AProp.Name]);
else
raise ERttiMappingError.CreateFmt(‚Ní thacaítear le TypeKind (%s) do %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 nó Target 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(‚Tá an foinse ar iarraidh: „%s“ don Property %s‘,
[M.SourceName, M.Prop.Name]);
Continue;
end;

if AReader.IsNull(M.SourceName) then
begin
if moIgnoreNull in AOptions then
Continue;
// Gan meicníocht Nullable/Optional, ní féidir NULL a shocrú go h-inmharthana.
Continue;
end;

V := AReader.GetValue(M.SourceName);

try
SetPropertyValue(ATarget, M.Prop, V);
if moDebug in AOptions then
begin
Msg := Format(‚Mappaíodh %s <- %s (%s)‘, [M.Prop.Name, M.SourceName, VarTypeAsText(VarType(V))]);
OutputDebugString(PChar(Msg));
end;
except
on E: Exception do
raise ERttiMappingError.CreateFmt(‚Earráid mappaíochta ag %s <- %s: %s‘,
[M.Prop.Name, M.SourceName, E.Message]);
end;
end;
end;

end.

Cad chuige é seo

Faigheann tú mappáil is féidir a mheas go soiléir i léirmheasanna cód:

  • Tá gach Property a mappáladh marcáilte go amhairc (airí).
  • Tá an claochlú lárnach, agus dá réir sin comhsheasmhach agus tástálaithe.
  • Insíonn téacsanna earráide cén Property agus cén foinse atá buailte.
  • Soláthraíonn mód dífhabhtaithe duit, más gá, an slabhra cruthúnais gan go mbeidh ort breakpoints sa phróiseas táirgthe.

Coinníollacha teoranta agus fabhtanna tipiciúla

  • NULL-Semantik: Gan coincheap Nullable féin (m.sh. Nullable<T> nó Option-Types) níl an “NULL socraithe” soiléir. Sa snipeit déanfar NULL a scipeáil de réir réamhshocraithe. Tá sé seo coimeádach agus cuireann sé cosc ar athscríobhanna ciúine.
  • TRttiContext-Lebensdauer: Tógann muid an cache uair amháin in aghaidh an chineáil agus caithimid an Context i ndiaidh sin amach. Is gnách é seo. Tá sé tábhachtach nach ndéanann tú RTTI-Context nua do gach socrú réimse.
  • Threading: Tá an cache cosanta trí Monitor. I mappálacha an-ardpharálae (m.sh. REST-Server) ba chóir duit freisin a sheiceáil an dtógfaidh tú an cache “te” ag an tús (Preload) chun laghdú ar Lock-Contention.
  • PropertyType Kind: tkClass agus tkSet níor cuireadh i bhfeidhm iad ar an gcúis. Do réada nasctha is fearr mappáil a dhéanamh go réitúil (le polasaí shoiléir) nó luach a shannadh de láimh go rialta.
  • Locale-Fallen: Tá varDouble trí VarAsType sách láidir, ach is fadhb iad stríoca mar „1,23“ i gcomparáid le „1.23“. Má sheolann do fhoinsí Strings, is minic gurb é an réiteach ná parser féin a chur i bhfeidhm (le Cultúr shonraithe).

Variante für FireDAC und TDataSet: Reader-Adapter statt Mapper-Kopplung

In BDE-Ablosung mit nativer Anbindung- nó i n-aipeanna VCL/Win32 clasaiceacha is minic gurb é an fhoinse TDataSet. In ionad an Mapper a cheangal le TField, scríobh adapter a chomhlíonann an comhéadan IValueReader. Buntáiste: fanann an Mapper neamhspleách ar an rochtain sonraí (tábhachtach más eolas duit rochtain sonraí a aistriú níos déanaí chuig Services nó chuig REST-Server).

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

Mar sin, tá mappáil shonrach mar seo:

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

Cathain is fiú an cur chuige — agus cá nach bhfuil sé

Bíonn an patrún seo de ghnáth úsáideach i trí chás:

  1. Nuachóiriú céim ar chéim: Ba mhaith leat réada réimse a chur i bhfeidhm gan an rochtain sonraí a athmhúnlú go hiomlán ó thús (go hiondúil i Delphi nuachóiriú i n-aipeanna atá ann cheana).
  2. Teorainneacha comhéadan: Iompórtálacha CSV/Excel, REST-Payloads nó foinsí sonraí „measctha“ a éilíonn tiontú láidir agus teachtaireachtaí earráide soiléire.
  3. Inmharthacht sa fhoireann: Cuireann airíonna rialacha mappála le feiceáil agus iad in-athbhreithnithe — rud an-úsáideach i stórchóid mhóra.

Tá teorainneacha úsáide chomh soiléir ann freisin:

  • Graif réada casta (Child-Collections, tagairtí timthrialla) nár chóir a mappáil go „automagach“. Is minic go mbíonn cód shoiléir nó patrún Assembler/Factory ar leith níos cobhsaí anseo.
  • High-Throughput-Hotpaths (m.sh. ETL sonraí maise) bainfidh siad níos mó sochar as mapeálaithe ginte i gcód nó mappáil optamaithe de láimh, fiú má tá RTTI taisceáilte.
  • Nullable/Optional is ábhar ar leith é. Má tá sé i ndáiríre riachtanach idirdhealú a dhéanamh idir „níl ann“, „NULL“ agus „réamhshocraithe“, ba cheart é sin a léiriú sa mhúnla réimse, ní a cheilt sa mappálaí.

Cuir i gcomhthéacs an ailtireachta agus an oibriúcháin

Ó thaobh na hailtireachta de is comhpháirt bonneagair é an mappálaí seo ar an teorainn idir léiriú sonraí agus an réimse. Ní chuireann sé deireadh le sraithiú glan, ach is féidir leis é a chumasú: is féidir go bhfanfaidh an rochtain sonraí (FireDAC, SQL, Views) pragmatach, agus an réimse fós comhsheasmhach. I gcórais ilshraitheacha (go minic ar a dtugtar Layer-3 ailtireacht: UI, Domain/Services, bonneagar) tá an mappálaí sa bonneagar agus úsáidtear é ag Services, ní ag foirmeacha UI.

Tábhachtach ó thaobh oibríochta: ná cumasaigh an moDebug go buan i seirbhísí táirgthe — cumasaigh é go spriocdhírithe amháin. Maidir le fadhbanna sonraí nach mbaintear amach go héasca iad a athchruthú, tá sé ciallmhar cosán diagnóisise lasctha a bheith agat (cumraíocht, feature-flag). Murach sin, d’fhéadfadh toirt logaí agus fo-iarmhairtí a bheith ann.

Conclúid: RTTI — sea, ach amháin le treoirlínte soiléire

Delphi RTTI don mapeáil gan draíocht oibríonn go maith má úsáideann tú RTTI mar uirlis do mheiteashonraí ráite — ní mar threoir do heuristicí ciúine. Airíonna mar opt-in, tiontú lárnaithe, taisce in aghaidh an chineáil agus téacsanna earráide intuigthe aistríonn an ábhar ó „neamhtrédhearcach“ go „oibriúil“. Tá an cur chuige beartaithe nach bhfuil uilíoch: do ghraif chasta, do shemantacht dhocht maidir le nialacha nó do fheidhmíocht uasta, beidh tógála breise riachtanach. Mar dhroichead chobhsaí idir struchtúir Dataset/Legacy agus réada fearainn níos nua-aimseartha, is é é i go leor bunachar cód Delphi an chéim phragmatach a dhéanann nuachóiriú indéanta ar chor ar bith.

Má tá tú i bhfeidhmchlár Delphi a d’fhorbair thar ama agus má tá tú sáite ag imill mapeála, caighdeán sonraí nó nuachóiriú céim ar chéim, is féidir linn é sin a shocrú go glan le chéile agus é a ailíniú le do ailtireacht: déan teagmháil.

Sa chomhthéacs ghairmiúil, tá Delphi Rtti Mapping agus Attribute Mapping Delphi i ról tábhachtach nuair is gá do chomhtháthuithe, sreafaí sonraí agus forbairt leanúnach comhoibriú go soiléir.

Tionscadal nó tionscnamh nuachóirithe a phlé le Net-Base.

Roinn an post

Roinn an t-alt seo go díreach

Tá LinkedIn, X, XING, Facebook, WhatsApp agus ríomhphost ar fáil láithreach. Do Instagram ullmhaímid nasc agus téacs gairid láithreach.

Ríomhphost

Osclaítear Instagram i gcluaisín nua. Cóipeáiltear an nasc agus an téacs gairid roimh ré isteach sa ghearrthaisce.