La table: campi, data type e identità
Una table in AL descrive insieme lo schema persistito e parte del comportamento del dato. Ogni field ha un ID numerico, un nome e un data type: l’ID è l’identità reale del campo lato database, il nome solo l’etichetta che il codice usa. Per questo un campo obsoleto si marca con ObsoleteState invece di sparire: cambiarne l’ID su una tabella pubblicata significa perdere il dato.
Dei data type contano le implicazioni. Code normalizza in maiuscolo ed è il tipo dei campi identificativi tipo “No.”; Text no. Decimal per gli importi, Integer e BigInteger per i contatori. DateTime è memorizzato in UTC e presentato nel fuso dell’utente, Date no. Blob, Media e MediaSet non arrivano con la lettura del record e vanno calcolati esplicitamente. Infine Option contro Enum: l’enum è un oggetto AL a sé, referenziabile e ampliabile da terzi con enumextension, l’Option è un elenco chiuso dentro il campo. Ogni field richiede poi una DataClassification, mentre TableRelation lo lega a un’altra tabella abilitando lookup e validazione referenziale.
Chiavi: ordinamento, indici e SIFT
La prima chiave dichiarata è la primary key: univoca, determina l’ordinamento naturale del record set e corrisponde all’indice clustered sul database. Le successive sono secondary key, cioè indici: ordinano con SetCurrentKey e rendono selettivi i filtri più frequenti. Vale il trade-off classico: ogni chiave accelera certe letture e appesantisce ogni scrittura, quindi si aggiungono sui pattern d’accesso reali. Una chiave può inoltre dichiarare SumIndexFields, il meccanismo SIFT su cui poggia l’aggregazione delle FlowField di tipo Sum.
FieldClass, CalcFormula e trigger
FieldClass distingue tre nature di campo. Normal è il dato persistito. FlowField non occupa spazio: il valore nasce da una CalcFormula (Sum, Average, Min, Max, Count, Lookup, Exist) valutata su un’altra tabella, quindi il campo è in sola lettura e va calcolato con CalcFields o dichiarato con SetAutoCalcFields, altrimenti in codice resta a zero o false. FlowFilter non contiene dati affatto: è un filtro che utente o codice impostano e che le CalcFormula consumano, il caso tipico è il Date Filter che restringe il periodo di una somma.
I trigger di tabella sono OnInsert, OnModify, OnDelete e OnRename, e non scattano da soli: Insert(true) esegue OnInsert, Insert() lo salta. Allo stesso modo OnValidate parte quando il valore arriva da una page o da Validate(), non da un’assegnazione diretta in AL: è la differenza fra scrivere un valore e farlo passare dalla logica di business. OnLookup sostituisce il lookup standard del campo.
Table extension e tipi di tabella
Una table extension aggiunge campi, chiavi secondarie, field group, procedure e codice di trigger a una tabella esistente senza toccarne il sorgente. Non può rimuovere o ridefinire i campi della base né cambiarne la primary key; per alterare il comportamento del codice standard non si modifica nulla, ci si aggancia con un event subscriber a un event publisher. I campi aggiunti devono avere ID dentro gli idRanges dichiarati in app.json — assegnati da Microsoft per AppSource, tipicamente la fascia 50000 per una Per-Tenant Extension: è questo spazio di ID separato che permette a più estensioni di estendere la stessa tabella senza collidere. I dati aggiunti vivono in una companion table unita alla base a runtime.
Restano tre nature di tabella. Quella normale persiste sul database. Una tabella temporanea non è un oggetto diverso: è una variabile Record marcata temporary, stessa struttura e stessa API ma dati in memoria, isolati per sessione, che non toccano il database né fanno scattare i trigger di tabella — ideale come buffer. Le tabelle di sistema appartengono alla piattaforma, stanno nella fascia di ID alta e non si creano; a esse si affiancano i campi di sistema di ogni tabella, fra cui il SystemId, il GUID stabile usato dalle API page.
Trappole tipiche d’esame
- La FlowField vale zero in codice o in un report → CalcFields o SetAutoCalcFields: la lettura del record non la calcola; su una page ci pensa la piattaforma, da cui l’illusione che funzioni solo a video.
- Serve un campo in più su una tabella standard → table extension, non una copia della tabella: duplicare la base rompe integrazioni, permission set e upgrade.
- Serve cambiare una validazione standard → event subscriber: la table extension affianca, non riscrive campi e logica esistenti.
- L’inserimento massivo non applica numerazione e default →
Insert(true): conInsert()OnInsert non viene eseguito; stessa logica perValidate()contro l’assegnazione diretta. - Filtro lento su campi non chiave → secondary key mirata più SetCurrentKey: va motivata, perché ogni indice pesa su insert, modify e delete.
- Valori che anche altre app dovranno ampliare → enum, non Option: solo l’enum è un oggetto AL estendibile con
enumextension.