Net-Base Maġazin

10.05.2026

Mapping minn Dataset għal Oġġett għal strutturi legacy mhux konvenzjonali: stabbli, faċilment iddebbaggjat, mingħajr maġija tal-ORM

Meta settijiet ta' dejta storikament akkumulati jkunu, il-mapper standard spiss jiffaċċjaw problemi ma' kolonni alias, taħlitiet tat-tipi u strutturi ta' join li jinbidlu. Dan il-porzjon ta' kodiċi sors juri mapping robust u li jista' jiġi debugġjat minn sett ta' dejta għal oġġett f'Delphi: b'pjan tal-mapping, konvertituri, semantika tan-null...

10.05.2026

F’sistemi mibnija maż‑żmien Delphi il-mapping minn Dataset għal Oġġett huwa rari l-każ nadif ta’ „kamp = proprjetà“. Fi softwer aziendali personalizzat issib minflok kolonni alias minn view, riżultati ta‘ Join b’ismijiet ta‘ kamp doppji, valuri „vojta“ bħala 0 jew ' ', kolonni tipizzati li illum jirritornaw VARCHAR u għada INTEGER, u kolonni li skond id-dialog ta‘ tiftix sempliċement mhumiex preżenti. Hekk ħafna mapper jaqgħu: jew isiru «maġiku» (u b’hekk diffiċli biex jiġu debugġġjati), jew huma daqshekk stritti li anki kamp fakultattiv jista‘ jieqaf l-operazzjoni.

Dan il-fragment tas-sors jurina mapper pragmatiku għal Delphi, li b’mod konxju mhux ORM, imma jindirizza b’mod nadif il-każijiet limiti tal-legacy: riżoluzzjoni unika ta‘ kamp, konverżjoni kontrollata, semantika ta‘ null, kampijiet fakultattivi u messaġġi ta‘ żball kontekstwali u segwejċi. Huwa adattat għal Data-Access-Layer (DAL, jiġifieri saff li jikkapsula l-aċċess tad-dejta) jew mudelli Repository – u jista‘ jiġi kkombinat tajjeb ma‘ BDE-Ablosung bi konnessjoni nativa (librerija ta‘ aċċess tad-dejta ta‘ Delphi għal ħafna DBs).

Għaliex il-mapping standard jonqos f’istrutturi eżistenti

Xi wħud mill-kawżi tipċi mill-operazzjoni li ftit jidheruhom f’riedesign „nadif“:

  • Isem ta‘ kamp ambigwu: Join jirritorna ID minn diversi tabelli; fid-dataset isemħu mbagħad ID, ID_1 jew jiġi ridenominat b’alias tal-SQL.
  • Nulls semantiċi: 0 jfisser „mhux magħruf“, '1899-12-30' huwa „mhux data“, ' ' ifisser „mhux imdaħħal“.
  • Tipi li jinbiddlu: View mhux qed jagħmel cast; id-driver jirritorna ftWideString minflok ftInteger. Il-konverżjoni tal-Variant issir sors ta‘ żball.
  • Kolonni fakultattivi: Dialog ta‘ tiftix juża, skond il-filtru, lista SELECT differenti. Il-kodiċi madankollu jistenna l-kampijiet “dejjem”.
  • Kapaċità ta‘ debug: Meta l-mapping jisparixxi f’RTTI, it-tfittxija għal żbalji fil-data tal-klijent issir diffiċli (liema kamp, liema valur, liema tip?).

Aproċċ: Pjan ta‘ mapping minflok konvenzjoni, b’konverżjoni kontrollata

Il-kern hu pjan ta‘ mapping: lista ta‘ regoli „Proprjetà X tiġi mill-kamp A jew B, hija fakultattiva/meħtieġa, tuża Konvertitur Y“. B’hekk il-mapping jibqa‘ deklarattiv, imma mhux „moħbi“ bħal f’diversi mekkaniżmi ORM. Barra minn hekk, il-mapper jista‘ jinqabad biex jarmi eċċezzjoni spjegattiva għal kull kamp, inkluż isem il-kamp, it-tip tad-data u l-valur raw.

Importanti: Aħna nimappjaw b’mod intenzjonat minn TDataSet, mhux minn klassi konkreta BDE-Ablosung mit nativer Anbindung. B’hekk jibqa‘ kompatibbli ma‘ TFDQuery, TClientDataSet jew komponenti ta‘ terzi.

Source-Schnipsel: Debugjabbli Mapping minn Dataset għal Oġġett għa kolonni legacy

Il-kodiċi jimplimenta:

  • Riżoluzzjoni tal-kamp permezz ta‘ lista ta‘ prijoritajiet (aliases/fallbacks)
  • Gestjoni ta‘ kampijiet meħtieġa u fakultattivi
  • Semantika ta‘ null permezz ta‘ konvertituri (z. B. 0 => Null)
  • Messaġġi ta‘ żball stabbli bil-kuntest
  • Hook ta‘ debug biex il-problemi tal-mapping ikunu jistgħu jiġu traċċati fil-prova jew f’każ ta‘ appoġġ

unit Legacy.DatasetMapper;

interface

uses
System.SysUtils, System.Variants, System.Generics.Collections, Data.DB;

type
EDataMappingError = class(Exception)
private
FFieldNames: string;
FTarget: string;
FDataType: string;
FRawValue: string;
public
constructor Create(const ATarget, AFieldNames, ADataType, ARawValue, AMsg: string);
property Target: string read FTarget;
property FieldNames: string read FFieldNames;
property DataType: string read FDataType;
property RawValue: string read FRawValue;
end;

TMapRequired = (mrOptional, mrRequired);

TMapDebugEvent = reference to procedure(
const TargetMember: string;
const SourceField: string;
const SourceType: TFieldType;
const SourceValue: Variant);

// Konvertitur jaċċetta Variant u jirtorna Variant (e.g. Null, Integer, String, TDateTime bħala Double)
TFieldConverter = reference to function(const V: Variant): Variant;

TFieldSpec = record
TargetMember: string;
SourceCandidates: TArray<string>;
Required: TMapRequired;
Converter: TFieldConverter;
class function Create(const ATarget: string; const ACandidates: array of string;
ARequired: TMapRequired; const AConverter: TFieldConverter): TFieldSpec; static;
end;

TLegacyDatasetMapper = class
private
FOnDebug: TMapDebugEvent;
function FindFieldByCandidates(DS: TDataSet; const Candidates: TArray<string>): TField;
function FieldTypeToString(FT: TFieldType): string;
function VariantToDiag(const V: Variant): string;
public
property OnDebug: TMapDebugEvent read FOnDebug write FOnDebug;

// MapOne: iċempel il-setter għal kull Spec. Mingħajr RTTI: assenjazzjoni espliċita hija aktar faċli għall-debug.
procedure MapOne(DS: TDataSet; const Specs: TArray<TFieldSpec>;
const Assign: TProc<string, Variant>);
end;

// Konvertituri ta‘ għajnuna
function C_TrimToNull: TFieldConverter;
function C_ZeroToNull: TFieldConverter;
function C_StrictInt: TFieldConverter;
function C_DateFromStringOrNull: TFieldConverter;

implementation

{ EDataMappingError }

constructor EDataMappingError.Create(const ATarget, AFieldNames, ADataType, ARawValue, AMsg: string);
begin
inherited Create(AMsg);
FTarget := ATarget;
FFieldNames := AFieldNames;
FDataType := ADataType;
FRawValue := ARawValue;
end;

{ TFieldSpec }

class function TFieldSpec.Create(const ATarget: string; const ACandidates: array of string;
ARequired: TMapRequired; const AConverter: TFieldConverter): TFieldSpec;
var
I: Integer;
begin
Result.TargetMember := ATarget;
SetLength(Result.SourceCandidates, Length(ACandidates));
for I := 0 to High(ACandidates) do
Result.SourceCandidates[I] := ACandidates[I];
Result.Required := ARequired;
Result.Converter := AConverter;
end;

{ TLegacyDatasetMapper }

function TLegacyDatasetMapper.FieldTypeToString(FT: TFieldType): string;
begin
Result := GetEnumName(TypeInfo(TFieldType), Ord(FT));
end;

function TLegacyDatasetMapper.VariantToDiag(const V: Variant): string;
begin
if VarIsNull(V) then Exit(‚NULL‘);
if VarIsEmpty(V) then Exit(‚EMPTY‘);
try
Result := VarToStr(V);
except
Result := ‚<unprintable variant>‘;
end;
end;

function TLegacyDatasetMapper.FindFieldByCandidates(DS: TDataSet; const Candidates: TArray<string>): TField;
var
Name: string;
begin
Result := nil;
for Name in Candidates do
begin
// Uża FindField minflok FieldByName: possibbli b’mod optional, mingħajr eċċezzjoni
Result := DS.FindField(Name);
if Result <> nil then
Exit;
end;
end;

procedure TLegacyDatasetMapper.MapOne(DS: TDataSet; const Specs: TArray<TFieldSpec>;
const Assign: TProc<string, Variant>);
var
Spec: TFieldSpec;
F: TField;
Raw, Val: Variant;
CandidatesJoined: string;
I: Integer;
FT: string;
begin
if (DS = nil) then
raise EArgumentNilException.Create(‚DS‘);
if not DS.Active then
raise EInvalidOperation.Create(‚Id-Dataset mhux attiv.‘);

for Spec in Specs do
begin
F := FindFieldByCandidates(DS, Spec.SourceCandidates);

if (F = nil) then
begin
if Spec.Required = mrRequired then
begin
CandidatesJoined := “;
for I := 0 to High(Spec.SourceCandidates) do
begin
if I > 0 then CandidatesJoined := CandidatesJoined + ‚, ‚;
CandidatesJoined := CandidatesJoined + Spec.SourceCandidates[I];
end;
raise EDataMappingError.Create(
Spec.TargetMember,
CandidatesJoined,
’n/a‘,
’n/a‘,
Format(‚Żball tal-mapping: il-kamp meħtieġ għal %s ma nstabx. Kandidati: [%s]‘,
[Spec.TargetMember, CandidatesJoined]));
end
else
Continue; // optionali: jinqabeż
end;

Raw := F.Value; // Variant; jikkunsidra Null
if Assigned(FOnDebug) then
FOnDebug(Spec.TargetMember, F.FieldName, F.DataType, Raw);

try
if Assigned(Spec.Converter) then
Val := Spec.Converter(Raw)
else
Val := Raw;

// Required: NULL wara l-konvertitur huwa żball (spiss aktar milli jħossu)
if (Spec.Required = mrRequired) and VarIsNull(Val) then
begin
FT := FieldTypeToString(F.DataType);
raise EDataMappingError.Create(
Spec.TargetMember,
F.FieldName,
FT,
VariantToDiag(Raw),
Format(‚Żball tal-mapping: %s huwa meħtieġ, imma l-valur huwa NULL wara l-konverżjoni. Kamp %s (%s), valur grezz=%s‘,
[Spec.TargetMember, F.FieldName, FT, VariantToDiag(Raw)]));
end;

Assign(Spec.TargetMember, Val);

except
on E: EDataMappingError do
raise;
on E: Exception do
begin
FT := FieldTypeToString(F.DataType);
raise EDataMappingError.Create(
Spec.TargetMember,
F.FieldName,
FT,
VariantToDiag(Raw),
Format(‚Żball fil-mapping f“%s minn kamp %s (%s), valur grezz=%s: %s‘,
[Spec.TargetMember, F.FieldName, FT, VariantToDiag(Raw), E.Message]));
end;
end;
end;
end;

{ Konvertituri }

function C_TrimToNull: TFieldConverter;
begin
Result := function(const V: Variant): Variant
var
S: string;
begin
if VarIsNull(V) or VarIsEmpty(V) then Exit(Null);
S := Trim(VarToStr(V));
if S = “ then
Result := Null
else
Result := S;
end;
end;

function C_ZeroToNull: TFieldConverter;
begin
Result := function(const V: Variant): Variant
var
N: Int64;
begin
if VarIsNull(V) or VarIsEmpty(V) then Exit(Null);
// jippermetti wkoll ‚0‘ bħala String
N := StrToInt64(Trim(VarToStr(V)));
if N = 0 then
Result := Null
else
Result := N;
end;
end;

function C_StrictInt: TFieldConverter;
begin
Result := function(const V: Variant): Variant
begin
if VarIsNull(V) or VarIsEmpty(V) then Exit(Null);
Result := StrToInt(Trim(VarToStr(V)));
end;
end;

function C_DateFromStringOrNull: TFieldConverter;
begin
Result := function(const V: Variant): Variant
var
S: string;
D: TDateTime;
begin
if VarIsNull(V) or VarIsEmpty(V) then Exit(Null);
S := Trim(VarToStr(V));
if (S = “) or (S = ‚1899-12-30‘) then Exit(Null);

// Strikt b’intenzjoni: m’hemmx ‚Try‘ li jnaqqas il-kwalità tad-dejta.
// Il-format jista‘ jvarja skont il-legacy; jekk meħtieġ, parametra hawn permezz ta‘ TFormatSettings.
D := ISO8601ToDate(S, False);
Result := D;
end;
end;

end.

Kif tuża l-Mapper prattikament (mingħajr RTTI, iżda xorta eleganti)

Il-Mapper iċempel callback Assign(TargetMember, Value). Dan iżomm l-assegnazzjoni esplicita (u b’hekk faċli biex tiċċekkja fil-debug) u jevita aċċessi RTTI fit-hot-path. Fil-prattika tibni għal kull oġġett/DTO (Data Transfer Object, jiġ. oġġett tat-trasport tad-data) żgħir ta‘ assegnazzjoni.

Delphi
type
  TCustomer = class
  public
    Id: Integer;
    ExternalNo: string;
    DisplayName: string;
    BirthDate: TDateTime; // optional in Legacy
  end;

function MapCustomer(DS: TDataSet; Mapper: TLegacyDatasetMapper): TCustomer;
var
  C: TCustomer;
  Specs: TArray<TFieldSpec>;
begin
  C := TCustomer.Create;
  try
    Specs := [
      TFieldSpec.Create('Id', ['CUSTOMER_ID', 'ID', 'C_ID'], mrRequired, C_StrictInt),
      TFieldSpec.Create('ExternalNo', ['EXT_NO', 'CUSTOMERNO'], mrOptional, C_TrimToNull),
      TFieldSpec.Create('DisplayName', ['NAME', 'DISPLAYNAME', 'C_NAME'], mrRequired, C_TrimToNull),
      TFieldSpec.Create('BirthDate', ['BIRTHDATE', 'DOB'], mrOptional, C_DateFromStringOrNull)
    ];

    Mapper.MapOne(DS, Specs,
      procedure(const Target: string; const V: Variant)
      begin
        if Target = 'Id' then
          C.Id := V
        else if Target = 'ExternalNo' then
          C.ExternalNo := VarToStrDef(V, '')
        else if Target = 'DisplayName' then
          C.DisplayName := VarToStr(V)
        else if Target = 'BirthDate' then
        begin
          if VarIsNull(V) then
            C.BirthDate := 0
          else
            C.BirthDate := V;
        end
        else
          raise EInvalidOperation.Create('Unbekanntes TargetMember: ' + Target);
      end);

    Result := C;
  except
    C.Free;
    raise;
  end;
end;

Skop: Il-mapping huwa deskritt ċentralment f’post (Specs), imma l-assegnazzjoni tibqa‘ esplicita. F’sitwazzjonijiet Legacy dan spiss ikun il-aħjar kompromess minflok mapping RTTI kompletament awtomatiku, peress li tara immedjatament liema property tiddependi fuq liema ismijiet tal-kamp.

Kundizzjonijiet: L-approċċ jistenna Dataset attiv u posizzjoni rekord attwali. Għal importazzjonijiet batch tirrepeti barra permezz ta‘ while not DS.Eof do u sejjaħ MapCustomer għal kull ringiela.

Perikli: Oqgħod attent għal VarToStr ma‘ BLOBs jew kampi Memo; hemm għandek tuża konvertituri proprji. U: „Required“ hawn tfisser wara l-konvertitur. Jekk C_TrimToNull jissetta kamp Required għal Null, dan hu intenzjonat — il-kwalità tad-data trid tiġi ċċarata fil-għajn jew fil-proċess.

Varjanti: Minflok string-targets tista‘ tuża wkoll Enum biex tevita żbalji tat-tip. Alternattivament tista‘ tissejvja l-funzjoni Assign għal kull Spec bħala TProc<Variant>, u mbagħad jitneħħa kompletament it-Target-String (ftit aktar boilerplate, iżda b’inqas klassi ta‘ żbalji).

Einordnung in Architektur: DAL/Repository, Logging und Betrieb

F’arkitettura b’saffi (tipikament: UI – Business – aċċess tad-dejta) dan il-mapping għandu jkun fis-saff ta‘ aċċess tad-dejta jew f’repository. Huwa importanti li l-dataset ma jintlaqax „mittendut“: oġġetti/DTOs huma l-interface iktar stabbli, speċjalment jekk aktar tard tgħaqqad REST-APIs jew tiddelega partijiet f‘ C# Services.

Għal il-operat u s-support jiswa l-debug-hook OnDebug. Tista 27 tu7ah f 27testijiet jew f 27ka7azijiet ta 27 appo79 31 riprodu A8ibbli biex tni BF 27 2al il-protokoll ta B9 liema kampi ġew effettivament mapjati. Fis-sistemi produttivi għandu jkun attivat b 0026#8209;mod selettiv u jista 27 jitwaqqaf, inkella l-logging jisir għali wisq jew jikkonsma wisq dejta.

U 017Zu effettiv tal-Debug-Hook

  • Unit-Tests: I E7 E7ekkja jekk statement SQL spe E7ifiku jagħti tassew il-kampijiet kollha me E7tie Eesorji.
  • Diagnosi: F 0027problemi tal-klijent tara immedjatament „il-kamp kien nieqes“ kontra „il-valur ma setax jiġi konvertit“.
  • Fażijiet ta E2 27 migrazzjoni: Fil-bidla ta E2 27 views u ismijiet tal-kolonni tista 0027 żżomm lists ta 0027 kandidati parallelament sakemm kollox jittrasferixxi.

Meta dan l-approċċ jfalli (u x E2 27inhuma l-alternattivi)

Il-mapping mid-dataset għall-oġġett mogħti hawn hu robust meta s-sors tad-dejta huwa instabbli u xorta jkollok bżonn mġieba deterministika. Tipikament jisfalli f 0027żewġ sitwazzjonijiet:

  • Ammonti kbar ħafna (eż. esportazzjoni massiv): il-konverżjoni ta E2 27 Variant u t-tfittxija b 00Isem tal-kamp tista 0027 ssir sinifikanti. F 0027dawk il-ka E2 27 jiswa cache prekomputat tal-indici tal-kampi għal kull SQL (eż. FieldByName darba għal kull Dataset, mhux għal kull ringiela).
  • Numru kbir ta E2 27 tipi DTO: Jekk tikteb mijiet ta 0027 mapper, il-kodi E7 E7 kodi F9e boilerplate isir problema. F 0027dawk il-ka E2 27 approċċ bba E7at fuq RTTI bi attributi jista 0027 jkun sensat E2 80 93 imma biss jekk tikkontrolla b 0027mod strett il-produzzjoni tad-debug u l-konvertituri.

Triq tajba ta E2 27 kompromess hi: ri EF EFsoluzzjoni tal-kampi u konverżjoni kif hawn (espliti E7i, tolerant tal-i EF BF Fb fejn me E7tie Eesorji), iżda billi tu 007a kodi E7 E7 ġenerat (eż. permezz ta E2 27 templates interni) minflok manwalment miktub.

Konklużjoni: Stabilità permezz ta E2 27 regoli espliti E7i E2 80 93 b 0027limiti ta E2 27 applikazzjoni ċari

F 0027Legacy-Datasets b 0027aliases, kolonni fakultattivi u semantika storika ta 0027 null, il-mapping mid-dataset għall-oġġett jaqbeż prinċipalment meta jibqa 00B4 esplitiċi u faċli għall-dijanjosi. Il-pjan tal-mapping magħmul minn lists ta 0027 kandidati, meħtieġa/fakultattiva u konverituri jagħmel eżatt dan: tista 0027 stabbilizza l-ereditajiet pass pass, mingħajr ma tidħol immedjatament ORM jew tinormalizza d-database f 0027daqqa.

Il-limiti jinsabu f 0027domini ta E2 27 prestazzjoni estrema u f 0027kull ka E2 27 fejn hemm numru kbir ta E2 27 tipi E2 80 93 f 0027dawk il-ka E2 27 għandek b 0027bżonn caching jew ġenerazzjoni awtomatika ta E2 27 kodi E7 E7. Għal software tan-negozju tipiku b 0027proċessi eżistenti, madankollu, l-approċċ huwa leva affidabbli biex l-aċċess tad-dejta u l-mudelli tad-domini jibqgħu deċuplati u faċli għall-manutenzjoni.

Jekk għandek mapping konkrètu ta E2 27 legacy (FireDAC, Views, proliferazzjoni ta‘ JOINs, semantika ta‘ Null) u tixtieq opinjoni sekondarja jew arkitettura ta E2 27 destinazzjoni affidabbli, il-pass li jmiss spiss hu anali E7i qasira b 0027e EF BF A8empji riprodu E7ibbli. Kuntatt:

Fil-kuntest professjonali wkoll Delphi Dataset Mapping u Legacy Delphi għandhom rwol importanti, meta l-integrazzjonijiet, l-istrimijiet tad-dejta u l-iżvilupp ikollhom jeħdmu flimkien b 0027mod nadif.

Iddiskutu proġett jew skema ta E2 27 modernizzazzjoni ma 0027 Net-Base.

Aqsam il-post

Aqsam dan il-post direttament

LinkedIn, X, XING, Facebook, WhatsApp u E-Mail huma immedjatament disponibbli. Għal Instagram nippreparaw il-link u t-test qasir direttament.

Imejl

Instagram jiftaħ f'tab ġdid. Il-link u t-test qasir jiġu kkopjati qabel fil-clipboard.