vai al contenuto principale

Modella relazioni

Estendiamo il modello con ulteriori classi tra loro correlate mediante relazioni di associazione e composizione.

Nella lezione precedente #

Abbiamo concluso la nostra digressione sull’importazione ed esplorazione dei dati. Riportiamo il focus sulla modellazione e ricapitoliamo cosa abbiamo visto fino a ora:

  • abbiamo modellato la classe Employee per gestire dati anagrafici di un insieme di impiegati. Attualmente è l’unica classe presente;
  • sul Database Schema, abbiamo modellato attributi nativi con vincoli e domini e dichiarato espressioni per rappresentare attributi derivati;
  • sull’Application Schema abbiamo definito Class warning.

Qualora non avessi chiari gli argomenti elencati sopra, prima di procedere ti consigliamo di ritornare sulle lezioni Crea la tua prima applicazione ed Estendi la tua applicazione.

Da una a piĂą classi #

In questa lezione estendiamo il nostro modello di partenza rappresentando nuovi concetti (e quindi nuove classi) tra loro correlati. Nei database relazionali è infatti comune che informazioni distinte (rappresentate su più tabelle) siano in qualche modo legate tra loro da una forma di relazione; ad esempio, un impiegato fa parte di un team, un impiegato partecipa a un progetto con un incarico di tot mesi, un progetto ha una lista di fatture, etc… Ci concentriamo quindi nell’espandere l’insieme di concetti satellite di Employee, come progetti, qualifiche, team e indirizzi; nel farlo, introdurremo nuove classi da collegare a Employee, modellando relazioni di diverso tipo e cardinalità.

Le associazioni e i ruoli #

Dalla Dashboard, arresta la Cloudlet Workforce e apri il modello TutorialEngine nel Designer, assicurandoti di aver selezionato la vista Database.

1. Aggiungi una nuova classe #

Per iniziare, rappresentiamo i progetti dell’azienda; per farlo, dobbiamo aggiungere una nuova classe, che chiamiamo Project:

  • Aggiungiamo degli attributi: ogni progetto ha un nome (name: string), che è anche l’object title della classe, e un flag (completed: boolean) che indica se il progetto è concluso o meno. Spunta required per il primo; per il secondo invece, dall’Application Schema , imposta il default value come costante booleana falsa.
  • Rendiamo i progetti unici, ma questa volta, invece di imporre un vincolo sul nome, sfruttiamo il tipo di dato serial, che permette di aggiungere un identificatore progressivo, univoco ed esplicito. Aggiungi quindi un attributo serial: serial e nota come su di esso viene imposto automaticamente un vincolo di unicitĂ  () che non può essere rimosso o modificato manualmente.

La classe Project appare come in figura. Attraverso ciascuna classe stiamo ora rappresentando un insieme indipendente di informazioni.

Designer project partial

2. Aggiungi un’associazione tra due classi #

Cursor database association bidirectional Colleghiamo la classe appena creata a Employee: clicca sull’icona della Palette Create a new bidirectional association; clicca su una classe e poi sull’altra (in questo caso l’ordine non è importante).

Abbiamo appena creato un’associazione, la più comune forma di relazione tra due classi. Nel modello, una relazione è rappresentata da una linea che connette una coppia di classi. Ciascuna delle classi che partecipano all’associazione assume un ruolo (role), raffigurato con un cerchio all’estremità della relazione.

Designer relations employee project

Pensa ai ruoli come a degli attributi “speciali”: quando un ruolo è selezionato nel diagramma, viene evidenziata anche la classe di cui quel ruolo fa parte. Questa classe è chiamata parent per quel ruolo (in alcuni casi è anche detta classe source), mentre la classe che quel ruolo rappresenta è chiamata target.

Nel caso in figura, cliccando sul ruolo di sinistra viene evidenziato il parent Employee (un impiegato, oltre ai suoi attributi, è caratterizzato da un insieme di progetti a cui lavora), mentre Project è la classe target puntata dal ruolo.

Designer relations employee project highlighted

3. CardinalitĂ , identificatori e navigabilitĂ  dei ruoli #

Il simbolo all’interno del ruolo rappresenta la sua cardinalità, ovvero quanti oggetti di quella classe possono essere associati a un oggetto della classe a cui quel ruolo si riferisce. Di default, le associazioni vengono create come molti-a-molti: l’asterisco * (any) sul ruolo della classe A indica che ogni oggetto A può essere associato a un numero qualunque di oggetti della classe B. Nel nostro caso, un impiegato può lavorare su un numero qualunque (anche nullo) di progetti.

Possiamo modificare la cardinalitĂ  di un ruolo dal Role menu, a cui possiamo accedere facendo click destro su di esso; combinando le cardinalitĂ  dei due ruoli possiamo definire associazioni uno-a-molti, uno-a-uno, oppure con cardinalitĂ  personalizzate. Per ora non modificare la cardinalitĂ  di questa associazione.

Designer role menu

Ciascun ruolo, analogamente a classi e attributi, ha un suo identificatore:

Puoi esaminare l’identificatore dal tooltip che appare passando col mouse sopra al ruolo. In questo caso, il cerchio dal lato di Project rappresenta il ruolo _project__ di Employee nell’ambito dell’associazione con Project, cioè _Employee.project__. Nel tooltip sono inoltre descritte le regole di cardinalità di quel ruolo in modo discorsivo, come in figura.

Designer project association tooltip

Per favorire la pluralizzazione, rinominiamo i ruoli: fai click destro sul ruolo _project__ e seleziona Rename dal Role menu; digita projects e premi Invio per confermare; ripeti il procedimento per il ruolo di _Employee_ e rinominalo in employees. Gli identificatori che abbiamo scelto riflettono la cardinalità dell’associazione: un employee lavora su più projects, e a un project lavorano più employees.

Alle estremità dell’associazione sono presenti due frecce. Queste indicano la navigabilità dell’associazione; in questo caso l’associazione è visibile da entrambe le classi ed è pertanto bidirezionale, ovvero è possibile da un impiegato risalire ai progetti a cui lavora, e viceversa. Dal Role menu possiamo spuntare Navigable per rimuovere la visibilità da una delle due classi e rendere l’associazione unidirezionale. Per ora, lascia l’associazione tra Employee e Project così com’è.

4. Usa le associazioni per estrarre nuove classi #

Osservando la classe Employee possiamo notare facilmente che esistono due problemi:

  • Ogni impiegato può ricoprire una piĂą una sola position tra quelle che abbiamo consentito nella lista di valori ammessi. Cosa succede se vogliamo aggiungere nuove posizioni? Cosa succede se vogliamo permettere di ricoprire piĂą di una posizione?
  • L’attributo team ha spesso un valore ripetuto, ma questo deve essere ogni volta inserito manualmente. Come facciamo a impedire di inserire nomi di team errati senza dichiarare a priori quali team sono ammissibili restringendo il dominio?

I problemi nascono dal fatto che in realtà informazioni come posizione e team non sono parte integrante del concetto di impiegato. Pertanto in questo caso le restrizioni del dominio non sono la strada giusta, perché dovremmo modificare il modello ogni volta che un utente vuole aggiungere una nuova posizione o un team dall’applicazione.

Sfruttiamo le relazioni per risolvere entrambi i problemi e aggiungiamo due nuove classi per “incapsulare” le informazioni relative a questi attributi:

  • Per prima cosa elimina gli attributi position e team da Employee. Nota come l’eliminazione degli attributi position e team comporterĂ  la rimozione delle corrispondenti colonne dal database e dei valori in esse memorizzati. A fine modellazione dovremo risolvere questa issue di media entitĂ .
  • Crea le classi Qualification e Team e aggiungi l’attributo label: string per Qualification e name: string per Team.
  • Imposta entrambi gli attributi come object title per le rispettive classi, spuntali come obbligatori e imponi inoltre un vincolo di unicitĂ  su ciascuno (non vogliamo due oggetti che rappresentino la stessa qualifica o due team con nomi identici).

Designer team qualification

Associamo Qualification a Employee in modo che un impiegato abbia al massimo tre qualifiche e che una qualifica possa essere condivisa da un numero qualsiasi di impiegati. Vogliamo inoltre che sia possibile risalire da un impiegato alla sua qualifica, ma non viceversa. Pertanto, creiamo un’associazione unidirezionale:

  • Cursor database association unidirectional clicca sull’icona Create a new unidirectional association, clicca prima su Employee e poi su Qualification. Stavolta l’ordine è importante, perchĂ© determina il verso dell’associazione;
  • apri il Role menu per il ruolo di Qualifications e seleziona Custom Cardinality: dal Cardinality Editor imposta 0 per MIN e 3 per MAX. Il ruolo riporterĂ  la lettera C a indicare la cardinalitĂ  personalizzata;
  • infine, rinomina questo ruolo in qualifications e quello di Employee in employees.

Associamo Team ad Employee in modo che un team abbia almeno un impiegato e che un impiegato appartenga al massimo a un team, stavolta consentendo la navigabilità in entrambe le direzioni (da un team si può risalire ai suoi membri):

  • crea un’associazione bidirezionale tra le due classi e scegli le cardinalitĂ  One or more (1N) per il ruolo di Employee e Zero or one (01) per quello di Team;
  • rinomina i ruoli rispettivamente in members e team (con una cardinalitĂ  a uno ha senso usare un identificatore al singolare).

Dopo queste modifiche, il modello appare come in figura:

Designer relations employee project qualification team

5. Aggiungi un’associazione riflessiva #

Supponiamo esista una gerarchia tra gli impiegati, per la quale alcuni devono supervisionare il lavoro di altri. Per rappresentare questa gerarchia potremmo creare delle qualifiche “speciali” – responsabile tecnico, CEO, capo progettista – che potrebbero essere ricoperte da un numero limitato di impiegati; tuttavia in questo modo stabiliremmo una gerarchia implicita basata sul livello di importanza di queste posizioni.

Nel nostro caso, considerando anche la presenza di più team, ci farebbe più comodo poter dire direttamente “chi è il supervisore di chi”, mettendo gli impiegati in relazione tra loro. Una relazione di questo tipo è detta riflessiva, poiché lega oggetti che appartengono alla stessa classe.

Per aggiungere un’associazione riflessiva, seleziona come sempre l’icona ma clicca poi due volte su Employee. Apparirà automaticamente un anello in basso a destra con due ruoli sulla stessa classe, chiamati di default employee_Source ed employee_Target; rinomina quest’ultimo in supervisor e cambia la cardinalità in Zero or one (01). Se non l’hai già fatto, rendi l’associazione unidirezionale da Source al Target.

Designer relations employee project qualification team recursive

In questo modo abbiamo stabilito che un impiegato può opzionalmente indicare un suo diretto supervisore. Ciascun impiegato può essere indicato come supervisore da più persone; da un impiegato possiamo risalire al suo supervisore, ma non viceversa.

Migliora la leggibilitĂ  del diagramma #

Spesso un modello viene aperto e modificato da piĂą persone, e il significato di alcuni suoi elementi possono risultare poco chiari a chi non li ha modellati. Per fornire informazioni aggiuntive, possiamo annotare il diagramma con dei commenti.

Designer employee supervisor comment 1

Cursor database comment Aggiungiamo un commento al ruolo Employee.supervisor : seleziona l’icona della Palette Create a new comment e clicca sul diagramma; nel post-it che appare, digita “Supervisor” e clicca altrove per confermare.

Cursor database comment link Clicca sul commento appena creato e poi sul ruolo Employee.supervisor, per collegarlo a quest’ultimo. Ora puoi spostare il commento trascinandolo per la maniglia a sinistra, ridimensionarlo con la maniglia nell’angolo in fondo a destra e piegare il link a piacere.

Le composizioni #

Finora, lavorando con le associazioni, abbiamo messo in relazione le nostre classi dando a ciascuna coppia uguale importanza. La composizione è invece una relazione in cui una classe appartiene o è parte dell’altra.

Aggiungiamo ulteriori elementi al modello e supponiamo di voler sofisticare la relazione che c’è tra un impiegato e i progetti a cui collabora, memorizzando la data di inizio e di fine dell’incarico. Supponiamo che un impiegato possa nel tempo avere più incarichi relativi allo stesso progetto; per reificare queste informazioni dobbiamo introdurre una nuova classe intermedia tra Employee e Project:

Per prima cosa, liberiamoci dell’associazione bidirezionale che avevamo inizialmente creato: fai click destro su di essa e seleziona Delete dall’Association menu.

Dopodiché, crea una classe Project_assignment per reificare l’associazione tra Employee e Project; aggiungi due attributi start_date: date e end_date: date e rendi il primo obbligatorio (possono esistere incarichi che non hanno ancora una data di fine, ma devono sempre avere una data di inizio). Per il momento rendi start_date object title.

Designer project assignment

Imponiamo un Class warning dall’Application Schema sulla nuova classe per impedire agli utenti di inserire una data di fine incarico precedente alla data di inizio: crea un warning su Project_assignment chiamato endDateInPast con espressione end_date < start_date che viene calcolato durante una SaveNew e una SaveExisting; digita il messaggio End date must be after the start date. e rendi il warning bloccante.

Designer warning date in past

1. Aggiungi una composizione #

Ora dobbiamo mettere Project_assignment in relazione con Employee e Project, presumibilmente tramite una coppia di associazioni; ragioniamo però su questa domanda: quanto è forte la dipendenza che una classe ha nei confronti dell’altra? Mentre Project e Employee sono concetti ben distinti (possono essere gestiti indipendentemente), un incarico ha senso di esistere solo se c’è un impiegato a svolgerlo. In casi come questo è più corretto usare una composizione.

Cursor database compositionSpostati sul Database Schema e clicca sull’icona della Palette Create a new composition, poi clicca prima su Employee e dopo su Project_assignment.

Designer relations composition employee project assignment

Come puoi notare in figura, questo tipo di relazione presenta un ruolo speciale ed è navigabile in una sola direzione. Il rombo presso Employee indica che questa relazione è una composizione da Employee a Project_assignment. In una composizione, il target di questo ruolo è detto classe part (la parte), mentre il parent è detto classe whole (il tutto). Rinomina il ruolo part in assignments e lascia la cardinalità di default a molti (*) (un impiegato può avere più incarichi).

Nota come siano scomparsi da Project_assignment sia il default class role che la cardinalità di classe; aggiungendo la composizione, infatti, abbiamo stabilito una dipendenza secondo cui non può esistere un incarico senza un impiegato che lo svolga; pertanto non sarà possibile creare oggetti project_assignment senza prima creare un oggetto employee.

Il prossimo passo è mettere in relazione Project_assignment e Project:

  • in questo caso crea una semplice associazione unidirezionale rivolta dalla prima alla seconda classe. Rinomina il ruolo della prima in assignments;
  • imposta la cardinalitĂ  del ruolo della seconda a Exactly one (1): ciascun incarico deve sempre far riferimento a un progetto, e un progetto può essere riferito da piĂą incarichi.

Le tre classi appaiono come in figura:

Designer relations employee project project assignment

Project_assignment reifica l’associazione da Employee verso Project aggiungendo informazioni come le date di inizio e fine dell’incarico. Naturalmente, così facendo abbiamo tolto, dalla classe Project, la possibilità di risalire agli impiegati che partecipano ai progetti.

Supponiamo ora che uno di questi impiegati ricopra la speciale posizione di direttore per uno specifico progetto, e che sia necessario poter risalire alle sue informazioni con un riferimento “diretto” che non passi per Project_assignment. Per fare ciò dobbiamo introdurre una nuova associazione più specifica tra Project ed Employee:

  • crea un’altra associazione unidirezionale rivolta da Project a Employee;
  • imposta a Exactly one (1) la cardinalitĂ  del ruolo di Employee: un progetto ha un solo direttore, un impiegato può dirigere piĂą progetti;
  • rinomina i ruoli rispettivamente in projects e director.

Una volta completate le modifiche, il diagramma appare come in figura:

Designer relations project project assignment employee qualification team

2. Composizioni e forme di riuso #

Una strategia diffusa nella modellazione è il riuso di elementi. Per prima cosa, supponiamo che un progetto possa essere stato richiesto da un committente esterno all’azienda:

Crea una classe Customer con due attributi: l’object title name: string per indicare la ragione sociale del cliente o dell’organizzazione, e address: string, che conterrà un generico indirizzo.

Designer customer

Collega Project a Customer con un’associazione bidirezionale, rinomina i ruoli rispettivamente in projects e customer e imposta la cardinalità di quest’ultimo a Zero or one (01). In questo modo permettiamo di registrare progetti senza committente, come nel caso di progetti interni all’azienda.

Designer relations project customer

Ora supponiamo di voler memorizzare un indirizzo anche per i nostri impiegati. Una soluzione potrebbe essere quella di aggiungere un altro attributo address per Employee. Tuttavia questo approccio comporta diversi problemi:

  • Cosa succede se vogliamo che i due attributi abbiano una struttura comune?
  • Cosa succede se ripetiamo il ragionamento considerando un insieme di attributi, invece che uno solo (ad esempio, usando piĂą attributi relativi all’indirizzo per memorizzare anche informazioni come la cittĂ  o il paese)?
  • Cosa succede se in futuro vogliamo rappresentare piĂą indirizzi per un dipendente (ad esempio, indirizzo di lavoro e indirizzo di residenza)?

Evidentemente un approccio del genere non può scalare. Una soluzione più flessibile è quella di incapsulare le informazioni in un’unica classe, da collegare a Employee e Customer con una coppia di composizioni a uno (e risolvere i tre problemi di sopra):

  • crea una nuova classe Address, trascina l’attributo address di Customer e rilascialo sulla nuova classe cliccando su Move here;
  • aggiungi gli attributi zip_code: string, city: string e country: string per memorizzare CAP, cittĂ  e nazione;
  • imponi poi una restrizione sul dominio di zip_code affinchĂ© sia una sequenza di lettere e numeri lunga esattamente cinque caratteri: usa l’espressione regolare ^[A-Za-z0-9]+$,e imposta sia Min length che Max length a 5);

Designer address

  • crea un object title composto dalla concatenazione dei valori tutti e quattro gli attributi (ti ricordi come si fa?);
  • collega Customer ed Employee ad Address con due composizioni, rinominando per entrambe il ruolo in address e impostando la cardinalitĂ  a Exactly one (1). In questo modo abbiamo riutilizzato degli attributi comuni racchiudendoli in una classe part condivisa da piĂą classi whole.

Designer relations composition employee address customer

3. Composizioni multiple e vincoli di unicitĂ  #

La cardinalità “esattamente a uno” è molto stringente, e prevede che per ciascun impiegato o committente debba sempre corrispondere un indirizzo. Ciò non vuol dire che impiegato e committente debbano condividere lo stesso indirizzo; anzi non possiamo mai avere un oggetto address che è part sia di un employee sia di un customer! Quando creiamo uno dei due oggetti whole, viene creato automaticamente anche un address part per quell’unico oggetto.

Tuttavia, nulla ci impedisce di memorizzare un address con tutti i campi vuoti, oppure di creare due address con tutti i campi identici. Proviamo a impedire che si verifichi il secondo caso e creiamo un vincolo di unicitĂ  su tutti e quattro gli attributi: selezionali e clicca su Make unique.

Un nuovo selettore Unique constraint options for parts appare sullo schermo: in caso di composizioni multiple come questa, il Designer ci permette di definire un vincolo di unicità specifico, in base alla “provenienza” dell’oggetto part. In questo modo possiamo evitare l’effetto collaterale per cui le informazioni di una classe andrebbero in conflitto con quelle di un’altra.

Designer address unique constraints options for parts

Seleziona l’opzione Only among 'address' belonging to the same 'Customer'; dopodiché, ripeti lo stesso procedimento selezionando invece Only among 'address' belonging to the same 'Employee'; appaiono due chiavi nel footer di Address; cliccando su una di esse viene evidenziato – oltre agli attributi coinvolti – anche il rombo della classe whole per cui quel vincolo è valido.

Designer relations composition employee address customer unique constraints

Il diagramma finale dovrebbe apparire come in figura:

Designer tutorial engine partial

Gestisci l’aggiornamento del modello #

1. Risolvi i problemi di allineamento #

In questa lezione abbiamo apportato moltissime modifiche al modello iniziale. Con buona probabilità abbiamo introdotto dei problemi di allineamento con il database della nostra Cloudlet. Puoi renderti conto di ciò salvando il modello: il Designer ci avverte immediatamente della presenza di high-severity compatibility issues.

Osserva la classe Employee: l’origine del problema è evidenziato in rosso. Poiché il nostro database conteneva in precedenza degli oggetti employee, aggiungendo la composizione con cardinalità minima uno abbiamo invalidato tutti gli oggetti attualmente presenti; nessuno di questi infatti rispetta il vincolo per cui deve essere associato a un oggetto address, visto che la relazione è stata introdotta dopo la loro creazione!

Designer high severity issue employee composition address

Dal Designer puoi cliccare sull’icona della Palette per leggere la issue, che conferma la nostra tesi. Livebase non è in grado di risolvere questo problema, ma possiamo farlo noi muovendoci in due modi: svuotare il database (un database senza record non viola nessun vincolo), oppure rilassare il vincolo (se permettiamo cardinalità minima zero i dati attualmente presenti non violano alcun vincolo).

Optiamo per la seconda opzione: modifica la cardinalità del ruolo Employee.address in Zero or one e salva di nuovo il modello. Come puoi vedere, questa modifica ha risolto il problema e il ruolo whole di Employee è tornato nero come prima.

Chiudi il Designer e accedi al pannello Database di Workforce per consultare le altre issues: l’eliminazione di position e team ha generato due medium severity issues che comportano la perdita dei valori memorizzati. Abbiamo introdotto volutamente questo cambiamento spostando l’informazione nelle nuove classi, pertanto per ora siamo obbligati ad accettarlo.

Questo scenario conferma quanto abbiamo detto in precedenza parlando di lavorare con dati reali: prima di trasferire molti dati su un’applicazione in costruzione, è buona pratica lavorare per un po’ con dati di prova, così da poter testare il funzionamento, accorgersi di eventuali errori o discrepanze con i requisiti di partenza, e infine correggere il modello con facilità (senza rischiare di dover gestire disallineamenti complessi).

Clicca su Resolve all issues e lascia che la piattaforma risolva le due issue sopracitate insieme a tutte quelle di tipo low severity che comporteranno l’aggiunta di nuove tabelle, colonne e relazioni al database.

2. Aggiorna la versione archiviata #

Come di consueto, aggiorna la versione archiviata di TutorialEngine trascinando l’engine dalla Cloudlet e rilasciandolo nell’Engine library; aggiungi il commento che vuoi per questa versione e conferma il salvataggio.

Quando archivi una versione, quelle precedenti dell’engine non vengono sovrascritte; anzi, sono tutte consultabili e disponibili dall’archivio delle versioni. Per aprirlo, fai click destro sul TutorialEngine nella library e seleziona Versions.... Dovresti avere quattro versioni archiviate: 1.0, 2.0, 3.0 e 4.0. Selezionando una versione della lista, possiamo utilizzare i bottoni in basso nel pannello per visualizzare il modello nel Designer (Open), scaricarlo come file .xml (Download), eliminare la versione dalla lista (Discard) oppure visualizzarne i dettagli (View details).

Queste opzioni sono analoghe a tecnologie comuni di version control come Git e ci consentono di trattare i modelli Livebase come codice sorgente, potendo consultare e modificare una particolare versione o ripristinarla in una Cloudlet.

Aggiorna e controlla l’applicazione #

Chiudi il pannello delle versioni di TutorialEngine, avvia Workforce e accedi alla client GraphiQL non appena Livebase avrà finito di rigenerare l’applicazione.

Nota come nello schema di Application siano comparsi nuovi servizi e strutture per tutte le classi aggiunte, e come per le classi Project_assignment e Address siano presenti solo i servizi in lettura, in quanto classi part. Nota anche come nelle strutture restituite in output è presente un campo per ogni associazione che permette di ottenere gli oggetti associabili per quel ruolo.

Prova a creare un nuovo oggetto employee con il servizio Employee___create, nota le modifiche alla struttura dati di input EmployeeCreate e verifica il corretto funzionamento delle cardinalitĂ  che abbiamo imposto per le relazioni; per esempio, associare piĂą di tre Qualifications causerĂ  una issue di tipo ROLE_CARDINALITY.

Prenditi qualche minuto per esplorare liberamente l’applicazione e notare come tutti gli aspetti di cui abbiamo parlato finora siano stati effettivamente tradotti in codice funzionante, inclusi i servizi, la navigabilità e i vincoli sulle cardinalità delle relazioni.

Conclusioni #

Questa lezione conclude la seconda parte del Tutorial: abbiamo compiuto un enorme passo in avanti nella scoperta di Livebase, trattando l’aspetto chiave delle relazioni tra classi. Concetti come ruoli e cardinalità sono di importanza notevole e spalancano le porte all’apprendimento di argomenti di modellazione avanzati.

Clicca sul bottone per scaricare il modello che abbiamo realizzato in questa lezione:

TutorialEngine_relations.xml

Nella prossima lezione… #

Arricchiremo le relazioni che abbiamo definito nel modello, rendendole piĂą specifiche e in linea con i requisiti;
inoltre, aggiungeremo ulteriori classi e relazioni per completare il modello del database: Modella query e filtri.