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
IDminn diversi tabelli; fid-dataset isemħu mbagħadID,ID_1jew jiġi ridenominat b’alias tal-SQL. - Nulls semantiċi:
0jfisser „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
ftWideStringminflokftInteger. 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.
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ż.
FieldByNamedarba 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.