Luna è un generatore di codice automatico per agevolare e standardizzare la scrittura delle classi che si occupano del salvataggio dei dati di una applicazione.

Il codice viene generato in linguaggio VB.Net (il codice in C# verrà implementato a breve) e può essere utilizzato sia in applicazioni Desktop che Web.

Tutto nasce dall' esigenza di scrivere in maniera ottimale e facilmente gestibile la parte più noiosa e ripetitiva di accesso ai dati, mantenendo uno standard facilmente comprensibile.

Non si deve incorporare nessun oggetto nel proprio progetto, e tutto il codice generato è facilmente modificabile o customizzabile dallo sviluppatore.

COME FUNZIONA?

Innanzitutto è necessario che la base dati dell'applicazione sia stata studiata e realizzata. Una volta fatto ciò, il database creato viene dato in input a Luna che si occuperà dell'effettiva creazione delle classi.

I componenti principali di Luna sono:
  • Struttura
  • Interprete
  • Convertitore

Struttura

Luna ha una sua struttura proprietaria logica di tabelle, campi e relazioni con cui mappare in memoria un database esistente in un formato standardizzato.

Interprete

E' presente un interprete specifico per ogni tipologia di base dati, quindi è virtualmente possibile supportare ogni tipo di base dati. L'interprete si occupa di collegarsi al database indicato, leggere la struttura di tabelle e relazioni e creare in memoria una struttura logica che lo ricalca 1 a 1 utilizzando le classi campo, tabella e relazione di Luna.

Convertitore

Una volta che la struttura di database è stata slegata dall'origine dati fisica, il convertitore si occupa di creare il codice che serve per interfacciarsi completamente con la base dati.

SORGENTI SUPPORTATE

Luna al momento supporta i database di tipo ACCESS (mdb e accdb) e SQL SERVER. Luna supporta inoltre un suo formato proprietario Luna Data Schema (*.lds). Ogni volta che si esegue una generazione di un qualsiasi progetto, nella cartella dei file generati da Luna e' presente il relativo file lds che permette di riaprire la struttura creata in maniera svincolata dal database originale.

ESEMPIO PRATICO

Luna crea il codice delle classi che rappresentano ogni tabella, il codice delle classi DAO che si occupano di interfacciare con il database gli oggetti logici e il codice SQL che potrebbe servire a replicare la struttura della base dati in un altro storage.

Veniamo alla pratica. Dunque iniziamo da una semplice base dati di prova contenente le tabelle Clienti e Telefoni collegate tra loro e cosi strutturate:



Step 1
In questa schermata è possibile selezionare l'origine dati su cui si andrà a lavorare. L'opzione Load View permette di utilizzare come sorgente per le classi non solo le tabelle ma anche le eventuali viste contenute all'interno del Database.

Nel nostro esempio dopo aver selezionato il tipo Database access e scelto il file, clicchiamo sul pulsante "Load DB";



Step 2
Nella schermata successiva ci verranno presentate tutte le tabelle [TABLE] o le eventuali viste [VIEW] che sono state trovate nella fonte dati selezionata. Per selezionare le tabelle che vogliamo generare basta mettere un segno di spunta sulla relativa casella.
Luna supporta la Pluralization, è sufficiente cambiare il nome della classe generata facendo doppio click sul relativo nodo nell'albero di destra. In questo esempio per la tabella Customers verrà generata la class Customer e cosi via. Selezioniamo tutto, cambiamo i nomi e clicchiamo sul pulsante "Create Code";



Cosa viene generato?
Innanzitutto Luna genera tre classi base, LunaBaseClass da cui ereditano tutte le altre classi create, LunaBaseClassEntity da cui ereditano tutte le altre classi entity e LunaBaseClassDAO da cui ereditano tutte le altre classi DAO. Inoltre viene creata una classe LunaSearchParameter di cui vedremo in seguito il funzionamento.

Dopodiche viene creata per ogni tabella una classe entity, una classe partial e una classe di accesso ai dati (DAO)

Classe Entity
La classe generata è un contenitore vuoto, che si integra con la relativa classe Partial e questo permette di accedere a tutti i metodi e le proprieta della classe Partial relativa, cosi organizzata:

#Region "Logic Field"
(vuota)
#End Region

#Region "Method"
(vuota)
#End Region

Viene poi creata una Classe Partial Tabella che eredita da LunaBaseClassEntity e che si integra con la relativa classe Entity, cosi organizzata:

#Region "Author"
...
End Region

#Region "Database Field Map"
(property con i campi della tabella gia pronti per la serializzazione opzionale)
End Region

#Region "Method"
Read(Id) as Tabella

Save(Tabella) as Integer (torna l'id inserito nel database)

SaveCascade(Tabella) as Integer (torna l'id inserito nel database)

#End Region

#Region "Embedded Class"
(property o liste di oggetti relazionate a questa classe)
#End Region

Viene infine creata una Classe TabellaDAO che eredita da LunaBaseClassDAO con i seguenti metodi:

Read(Id) as Tabella

Save(Tabella) as Integer (torna l'id inserito nel database)

SaveCascade(Tabella) as Integer (torna l'id inserito nel database)

Delete(Id)

Delete(Tabella)

GetAll()

Find()

ReadSerialize()

SaveSerialize()

Nell'esempio specifico:
  • Customer; mappatura della tabella Customers eredita da LunaBaseClassEntity
  • Phone; mappatura della tabella Phones eredita da LunaBaseClassEntity
  • CustomerGroup; mappatura della tabella CustomerGroups eredita da LunaBaseClassEntity
  • CustomersDAO; metodi di lettura, salvataggio, ricerca, lista ed eliminazione della classe Customer eredita da LunaBaseClassDAO
  • PhonesDAO; metodi di lettura, salvataggio, ricerca, lista ed eliminazione della classe Phone eredita da LunaBaseClassDAO
  • CustomerGroupsDAO; metodi di lettura, salvataggio, ricerca, lista ed eliminazione della classe CustomerGroup eredita da LunaBaseClassDAO



Analizziamo i vari metodi e proprietà generati:

Classi entity

Read(ID) - Questo metodo permette di leggere dal db un determinato oggetto in base al suo Id; all'interno viene istanziata la sua relativa classe DAO ed effettuate tutte le letture;
Save() - Questo metodo si occupa della persistenza dei dati, all'interno viene istanziata la relativa classe DAO che effettua il salvataggio. E' importante sapere che viene gestita l'effettiva modifica, infatti saranno effettuati solo i salvataggi degli oggetti nuovi o degli oggetti realmente modificati, per evitare un uso inutile del DB.
SaveCascade() - Questo metodo si occupa di salvare l'oggetto e tutti gli oggetti figli relativi;

Classi DAO

Read, Save e SaveCascade, sono gli stessi della classe entity. In più oltre ai metodi di cancellazione (Delete) abbiamo i metodi Find e GetAll che si occupano delle ricerche e ritornano una List(of Tabella) tipizzata.
Infine ci sono i metodi ReadSerialize e SaveSerialize che si occupano di leggere o scrivere su file una copia XML dell'oggetto che gli viene passato.



E ora che abbiamo il codice?
Ora la cosa piu semplice è cliccare sul pulsante Save e selezionare una cartella dove Luna salverà tutti i file che sono stati generati.

Poi avviamo Visual Studio e creiamo un progetto di prova di tipo Applicazione Windows Form e poi aggiungiamo nella stessa soluzione un progetto di tipo Libreria di Classi.
Innanzitutto nel progetto Windows Form aggiungiamo i riferimenti al progetto Libreria di classi. Nel progetto Libreria di classi clicchiamo su Aggiungi elemento esistente, selezioniamo tutti i file che sono stati salvati da Luna e il gioco è fatto. Oppure li trasciniamo direttamente dentro da Esplora Risorse con il drag and drop.

IMPORTANTE!!! Scrivete tutti i vostri metodi logici o personali e le proprietà custom nelle classi Entity, in modo che se dovete rigenerare il Database con Luna, non perderete nulla del codice aggiunto.



Connessione al db
La classe LunaBaseClassDAO da cui ereditano tutte le classi DAO ha tre costruttori della New:
  • New() da per scontato che ci sia una string di nome ConnectionString nei settings del progetto con la relativa stringa di connessione e si occupa da sola di crearsi la connessione (comodo per le Web Application);
  • New(Connection) accetta in input un oggetto di tipo connection;
  • New(ConnectionString) accetta in input una ConnectionString, passando una stringa vuota la BaseClass non apre la connessione(utile per i soli metodi di serializzazione);
La connessione è di tipo Shared quindi se in una procedura dovete istanziare vari oggetti e non volete usare la connection string vi basterà usare una sola dichiarazione New in cui specificate la connessione, nelle altre non sara' necessario.

Ovviamente nelle applicazioni desktop o con Connessione permanente basterà commentare le chiamate nella New() della LunaBaseClassDAO e commentare la dichiarazione della connessione _Cn rendendola pubblica, e tutto continuerà a funzionare.

Importante! Luna non si occupa di chiudere la connessione al Database. La funziona di chiusura viene inserita in ogni classe DAO ma non viene chiamata per lasciare allo sviluppatore la decisione su come gestire e quando chiudere la connessione.

Save

Per fare un inserimento nel nostro caso di un Cliente basterà scrivere:

Dim Cust As New Customer

Cust.CustomerName = "Homer Simpson"
Cust.Address = "Evergreen Terrace, 740"

Dim IdInserted As Integer = Cust.Save
If IdInserted = 0 Then MessageBox.Show("Error")

oppure

Dim Cust As New Customer

Cust.CustomerName = "Homer Simpson"
Cust.Address = "Evergreen Terrace, 740"

Dim MgrCust as new CustomersDAO

Dim IdInserted As Integer = MgrCust.Save(Cust)
If IdInserted = 0 Then MessageBox.Show("Error")

SaveCascade

Per fare un inserimento nel nostro caso di un Cliente con due telefoni basterà scrivere:

Dim Cust As New Customer

Cust.CustomerName = "Homer Simpson"
Cust.Address = "Evergreen Terrace, 740"

Dim Phone1 As New Phone
Phone1.PhoneNumber = "555.123"

Cust.ListPhone.Add(Phone1)

Dim Phone2 As New Phone
Phone2.PhoneNumber = "555.456"

Cust.ListPhone.Add(Phone2)

Dim Group As New CustomerGroup
Group.Read(2)

Cust.CustomerGroup = Group

Dim IdInserted As Integer = Cust.SaveCascade
If IdInserted = 0 Then MessageBox.Show("Error")

Tutti i metodi Save e SaveCascade ritornano l'Id appena inserito nel Database. Nell' esempio sopra il SaveCascade si occupa di salvare il nuovo Customer e di salvare i relativi oggetti Phone.

Ricerca

Per il cerca Luna crea in ogni classe DAO due metodi;

il metodo GetAll che torna una List (Of ClasseOggetto) di tutti gli oggetti di quella classe, a cui è possibile passare eventualmente un criterio di ricerca.

Il metodo Find invece ritorna una List (Of ClasseOggetto) in base a uno o più parametri di ricerca.

Il metodo si aspetta un Array di parametri e combina la ricerca in base a tutti i parametri che vengono passati.
I parametri sono oggetti di tipo LunaSearchParameter, vediamo due semplici esempi:

Cerchiamo tutti i telefoni di un determinato Customer

'IMPOSTIAMO IL PARAMETRO
Dim Par as new LunaSearchParameter("CustomerID",1)

Dim Mgr as New TelefoniDAO()
Dim ListPhone as List (Of Phone) = Mgr.Find(Par)

Cerchiamo tutti i telefoni di un determinato Customer che iniziano per 06

'IMPOSTIAMO IL PRIMO PARAMETRO NEL COSTRUTTORE
Dim Par as new LunaSearchParameter()
Par.FieldName = "CustomerID"
Par.Value = 1

'IMPOSTIAMO IL SECONDO PARAMETRO IN MODO ESPLICITO
Dim Par2 as new LunaSearchParameter("PhoneNumber","06%","LIKE")

Dim Mgr as New TelefoniDAO()
Dim ListPhone as List (Of Phone) = Mgr.Find(Par,Par2)


Classi figlie

In ogni classe vengono creati gli accessi agli oggetti delle classi figlie. In ogni caso si tratta di caricamenti pigri, ossia che vengono effettuati solo se effettivamente richiamati.

In caso di relazioni tra le classi di tipo 1 a 1 viene creato in ogni ClassePadre una property di tipo ClasseFiglio.

In caso di relazioni tra le classi di tipo 1 a N viene creata in ogni ClassePadre una List Of (ClasseFiglio).

Sia l'oggetto ClasseFiglio che la Lista di oggetti ClasseFiglio, sono caricati solo nel momento in cui realmente si accede ai metodi.

Questo è solo un semplice esempio, capire il funzionamento dei vari metodi creati è banale. La cosa importante è che non è stato necessario scrivere una riga di codice per interfacciarsi con la base dati.

IMPLEMENTAZIONI PROGRAMMATE

In ordine di importanza:
  • Ambito e transazioni distribuite
  • Le relazioni tra campi vengono lette solo da MS Access, sto scrivendo la parte che le rileva anche da MS Sql Server
  • Interprete per DB SQL Compact 4
  • Ampliare le informazioni mostrate sulle tabelle caricate dal Database
  • Interprete per DB Oracle
  • Interprete per DB Orient

DOWNLOAD

Luna è opensource e liberamente scaricabile e utilizzabile:

Codice Sorgente - http://www.diegolunadei.it/luna/lunasource.rar

Package Eseguibile - http://www.diegolunadei.it/luna/luna.rar

CONCLUSIONI e RIFERIMENTI

Mi sento di dire solo una cosa, non ho inventato nulla. Luna è semplicemente un tool gratuito che può aiutare lo sviluppatore nella stesura di codice ripetitivo per evitare errori e consentire una standardizzazione e pulizia del codice.
Scrivere codice manutenibile e facile da gestire anche da altri non è una cosa semplice, specie in grandi gruppi di lavoro. Inoltre interfacciarsi con il database per le operazioni standard diventa un operazione banale.

Per ogni riferimento, domanda, suggerimento o insulto potete trovarmi qui:

http://www.diegolunadei.it e qui d.lunadei@gmail.com

Un ultima cosa, Luna è un progetto in continua evoluzione quindi prendete la documentazione di questo tutorial come indicativa del funzionamento generale perchè mantenerla aggiornata è un lavorone. Per vedere cosa effettivamente crea vi consiglio di scaricare il package ed eseguire una generazione.