Guarda! Senza mani! - Ottava parte
a cura di Sabrina Cosolo e Diego Cattaruzza (requisiti: conoscenza intermedia di Sql Server e di .Net)Iniziamo facendo un po' di disastri
Prima di costruire le classi mancanti per avere il necessario a creare un database, facciamo un po' di rivoluzioni, in questo modo vediamo cosa accade nel mondo reale quando, mentre si sta sviluppando una applicazione, si trova una brillante soluzione ad un problema e la si vuole applicare a tutto ciò che si ha già scritto.nota: il codice esposto in questo articolo è lo stretto indispensabile a comprendere l'articolo stesso, essendo questo molto lungo, questa volta. Il codice completo è ovviamente fornito come al solito (vedi link a fondo pagina).
Mentre implementavamo la seconda collection del progetto, ci siamo resi conto che c'è un test ripetitivo che merita di essere trasformato in un metodo, pertanto abbiamo generato, nella libreria TAndT.Base, la nuova classe StringHelper, anche questa statica, in cui abbiamo messo il metodo:
namespace TAndT.Base { public static class StringHelper { public static bool IsNullOrTrimEmpty(string pStringValue) { return (pStringValue == null || pStringValue.Trim().Length == 0); } } }Public Class StringHelper Public Shared Function IsNullOrTrimEmpty(ByVal stringValue As String) As Boolean Return stringValue Is Nothing OrElse stringValue.Trim.Length = 0 End Function End ClassIl metodo implementato è IsNullOrTrimEmpty, il quale verifica che una stringa non sia nulla né vuota.
Nella nostra logica, una stringa è vuota quando non contiene nulla oppure quando contiene solo spazi.
Questa è una regola di business, applicabile ovunque serve, diversa da quella implementata dalla classe di sistema String dove, giustamente, una stringa con degli spazi non è considerata vuota.Generato il metodo, andiamo a sostituire in tutte le classi già implementate i controlli:
if( miastringa != null && miastringa.Trim().Length > 0 )If miastringa IsNot Nothing AndAlso miastringa.Trim().Length > 0 Thencon
if( !StringHelper.IsNullOrTrimEmpty(miastringa) )If Not StringHelper.IsNullOrTrimEmpty(miastringa) Thened i controlli:
if( miastringa == null || miastringa.Trim().Length == 0 )If miastringa Is Nothing OrElse miastringa.Trim().Length = 0 Thencon
if( StringHelper.IsNullOrTrimEmpty(miastringa) )If StringHelper.IsNullOrTrimEmpty(miastringa) ThenDove non presente, è opportuno inserire la direttiva:
using TAndT.Base;Imports TAndT.BaseEcco dove cercare:
Classi Membri IoHelper CheckEmptyPath CreateAppConfigFileName CreateUsrConfigFileName TAndTSetting IsValid TAndTSettings SetFromDataTable DbFileGrowth IsValid DbFileMaxSize IsValid DbFileSize IsValid UsingSqlServerConfig CnStringMaster CnStringMsdb IsValid Applicando questa modifica e scorrendo le nostre classi collection ed entità, ci siamo accorti che, nell'implementazione della generazione della DataTable all'interno della collezione TAndTSettings, abbiamo utilizzato i nomi delle proprietà della classe TAndTSetting per i nomi dei campi, e queste stringhe appaiono anche all'interno dell'entità come parametri dell'evento OnPropertyChanged della classe.
Per coerenza, quindi, abbiamo deciso di mettere le costanti con i nomi dei campi all'interno della classe Entity (TAndTSetting) rendendoli pubblici, in modo che possano essere usati sia dall'implementazione di OnPropertyChanged che nella generazione delle DataTable nella collezione.public const string FLD_NAME = "Name"; public const string FLD_VALUE = "Value";Public Const FLD_NAME As String = "Name" Public Const FLD_VALUE As String = "Value"Queste due costanti sono ora nella classe TAndTSetting; sono conseguentemente cambiate le proprietà Name e Value.
Abbiamo aggiunto, già che c'eravamo, una costante di formato, FMP_Tostring, modificando poi il metodo ToString.private const string FMP_ToString = "{0} = {1}"; // ... public string Name { // ... set { mName = value; OnPropertyChanged(new PropertyChangedEventArgs(FLD_NAME)); } } // ... public string Value { // ... set { mValue = value; OnPropertyChanged(new PropertyChangedEventArgs(FLD_VALUE)); } } public override string ToString() { try { return (string.Format(FMP_ToString, this.Name, this.Value)); } // ... }Public Const FMP_ToString As String = "{0} = {1}" Public Property Name() As String ' ... Set(ByVal value As String) mName = value OnPropertyChanged(New PropertyChangedEventArgs(FLD_NAME)) End Set End Property Private mValue As String Public Property Value() As String ' ... Set(ByVal value As String) mValue = value OnPropertyChanged(New PropertyChangedEventArgs(FLD_VALUE)) End Set End Property Public Overrides Function ToString() As String Try Return String.Format(FMP_ToString, Me.Name, Me.Value) ' ... End Try End FunctionDi conseguenza, nella classe TAndTSettings variano i metodi GetDataTable e SetFromDataTable:
public DataTable GetDataTable(string pTableName) { try { System.Data.DataTable dt = new System.Data.DataTable(); dt.TableName = pTableName; dt.Columns.Add(TAndTSetting.FLD_NAME, typeof(string)); dt.Columns.Add(TAndTSetting.FLD_VALUE, typeof(string)); dt.PrimaryKey = new System.Data.DataColumn[] { dt.Columns[TAndTSetting.FLD_NAME] }; foreach (TAndTSetting stt in this) { System.Data.DataRow dr = dt.NewRow(); dr[TAndTSetting.FLD_NAME] = stt.Name; dr[TAndTSetting.FLD_VALUE] = stt.Value; dt.Rows.Add(dr); } return (dt); } // ... } public void SetFromDataTable(System.Data.DataTable pDt, bool pClearCollection) { try { // ... for (int i = 0; i < pDt.Rows.Count; i++) { System.Data.DataRow drV = pDt.Rows[i]; string name = FieldValue<string>.Value(drV[TAndTSetting.FLD_NAME]); string value = FieldValue<string>.Value(drV[TAndTSetting.FLD_VALUE], string.Empty); if (StringHelper.IsNullOrTrimEmpty(name)) { SetSetting(name, value); } } } // ... }Public Function GetDataTable(ByVal tableName As String) As DataTable Try Dim dt As New DataTable dt.TableName = tableName dt.Columns.Add(TAndTSetting.FLD_NAME, Type.GetType("System.String")) dt.Columns.Add(TAndTSetting.FLD_VALUE, Type.GetType("System.String")) dt.PrimaryKey = New DataColumn() {dt.Columns(TAndTSetting.FLD_NAME)} For Each stt As TAndTSetting In Me Dim dr As DataRow = dt.NewRow dr(TAndTSetting.FLD_NAME) = stt.Name dr(TAndTSetting.FLD_VALUE) = stt.Value dt.Rows.Add(dr) Next ' ... End Function Public Sub SetFromDataTable(ByVal dt As DataTable, ByVal clearCollection As Boolean) ' ... For i As Integer = 0 To dt.Rows.Count - 1 Dim drV As DataRow = dt.Rows(i) Dim name As String = FieldValue(Of String).Value(drV(TAndTSetting.FLD_NAME)) Dim value As String = _ FieldValue(Of String).Value(drV(TAndTSetting.FLD_VALUE), String.Empty) If StringHelper.IsNullOrTrimEmpty(name) Then SetSetting(name, value) End If Next ' ... End SubCome potete notare, le costanti sono usate con il nome proprio nella classe Entity, mentre nella classe collection sono referenziate usando il nome della classe Entity che le contiene.
Analogamente, cambia il metodo ConfiguraDataGrid nella FrmConfig, e bisogna aggiungere la direttiva del caso:using TAndT.Base.Entities; // ... private void ConfiguraDataGrid() { // ... DataGridViewColumn col = this.dgApp.Columns[TAndTSetting.FLD_NAME]; // ... col = this.dgApp.Columns[TAndTSetting.FLD_NAME]; // ... col = this.dgUser.Columns[TAndTSetting.FLD_NAME]; // ... col = this.dgUser.Columns[TAndTSetting.FLD_VALUE]; // ... }Imports TAndT.Base.Entities ' ... Private Sub ConfiguraDataGrid() ' ... Dim col As DataGridViewColumn = dgApp.Columns(TAndTSetting.FLD_NAME) ' ... col = dgApp.Columns(TAndTSetting.FLD_VALUE) ' ... col = dgUser.Columns(TAndTSetting.FLD_NAME) ' ... col = dgUser.Columns(TAndTSetting.FLD_VALUE) ' ... End SubContinuando con le messe a punto, abbiamo deciso che il posto corretto ove inserire il metodo VerificaSeIlFormEAperto implementato su FrmMain per il test della nostra prima form non è quello giusto: infatti, se implementassimo un'altra applicazione di sicuro potremmo volerlo applicare, pertanto abbiamo modificato la libreria TAndT.UI, aggiungendo una cartella di nome Forms, dentro la quale abbiamo creato una nuova classe statica FormHelper, cui abbiamo aggiunto la direttiva al namespace System.Windows.Forms e vi abbiamo trasferito il metodo che si trovava sulla FrmMain, modificato nel modo seguente:
// ... using System.Windows.Forms; namespace TAndT.UI.Forms { public static class FormHelper { // ... public static Form VediSeIlFormEAperto(string pName, Form pMdi) { try { Form retForm = null; foreach (Form child in pMdi.MdiChildren) { if (child.Name == pName) { retForm = child; break; } } return (retForm); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } } } }Imports System.Windows.Forms Namespace Forms Public Class FormHelper ' ... Public Shared Function VediSeIlFormEAperto(ByVal name As String, ByVal mdi As Form) As Form Try Dim retForm As Form = Nothing For Each child As Form In mdi.MdiChildren If child.Name = name Then retForm = child Exit For End If Next Return retForm Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End Function End Class End NamespaceCome potete vedere, abbiamo passato la form MDI come parametro al metodo, che restituisce il form, se è aperto, oppure nulla.
Per praticità d'uso e per non far divenire l'MDI una form con 12000 righe di codice, abbiamo anche deciso che le funzioni di apertura delle form verranno spostate all'interno delle form stesse. Pertanto, il codice che prima si trovava all'interno del metodo configuraToolStripMenuItem_Click, costituisce il seguente metodo:
public static void OpenMe(Icon pIcon, Form pMDI) { try { FrmConfig frm = (FrmConfig)FormHelper.VediSeIlFormEAperto("FrmConfig", pMDI); if (frm == null) { frm = new FrmConfig(); frm.Icon = pIcon; frm.MdiParent = pMDI; frm.Show(); } else { frm.WindowState = FormWindowState.Normal; frm.BringToFront(); } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Public Shared Sub OpenMe(ByVal icon As Icon, ByVal mdi As Form) Dim frm As FrmConfig = DirectCast(FormHelper.VediSeIlFormEAperto("FrmConfig", mdi), FrmConfig) If frm Is Nothing Then frm = New FrmConfig frm.Icon = icon frm.MdiParent = mdi frm.Show() Else frm.WindowState = FormWindowState.Normal frm.BringToFront() End If End SubEssendo statico, questo metodo non necessita dell'istanza della classe form di cui è membro - nota per coloro cui sembrerebbe una contraddizione una classe che istanzi se stessa e si definisca come la nuova istanza.
Questo metodo è stato inserito all'interno della classe FrmConfig, cui abbiamo aggiunto la direttivausing TAndT.UI.Forms;Imports TAndT.UI.Formspermettendoci di ridurre il codice della FrmMain a questo:
private void configuraToolStripMenuItem_Click(object sender, EventArgs e) { FrmConfig.OpenMe(this.Icon, this); }Private Sub ConfiguraToolStripMenuItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ConfiguraToolStripMenuItem.Click FrmConfig.OpenMe(Me.Icon, Me) End SubDa ora in avanti, tutte le form che implementeremo avranno al loro interno il metodo OpenMe per la propria gestione.
Per il momento abbiamo fatto una sufficiente quantità di confusione, pertanto passiamo a fare cose più serie.
La classe FileSpecification
Dopo aver implementato le tre mini classi contenenti le dimensioni, implementiamo il contenitore di queste classi, ovvero la classe che ci permetterà di dare a SQL Server la specifica per ognuno dei file che comporranno un database, la classe FileSpecification.
E' una classe che contiene dati, una classe che potremmo voler salvare su disco, quindi risponde alle caratteristiche peculiari dell'interfaccia IEntity, però, essendo una classe specifica per SQL Server, la metteremo nella libreria TAndT.Data.
Aggiungiamo quindi alla libreria TAndT.Data, nella cartella Entities, la classe FileSpecification.
Fatto salvo per i diversi campi, questa classe non è diversa dalla classe TAndTSetting, pertanto in questo articolo inseriremo solo le cose peculiari, tralasciando i metodi già visti e spiegati nella Parte 2.using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.Xml.Serialization; using TAndT.Base; using TAndT.Base.Entities; using TAndT.Base.Xml; namespace TAndT.Data.Entities { [Serializable, XmlRoot(Namespace = "http://www.visual-basic.it")] public class FileSpecification : IEntity { public const string FLD_FileName = "FileName"; public const string FLD_Growth = "Growth"; public const string FLD_MaxSize = "MaxSize"; public const string FLD_Name = "Name"; public const string FLD_Size = "Size"; private const string FMP_ToSqlFileName = "FILENAME '{0}', "; private const string FMP_ToSqlGrowth = "GROWTH = {0} )"; private const string FMP_ToSqlMaxSize = "MAXSIZE = {0}, "; private const string FMP_ToSqlName = "( NAME = {0}, "; private const string FMP_ToSqlSize = "SIZE = {0}, "; private const string FMP_ToStringFileName = "Path: {0}"; private const string FMP_ToStringGrowth = "Valore di crescita automatica: {0}"; private const string FMP_ToStringMaxSize = "Dimensione massima ammessa: {0}"; private const string FMP_ToStringName = "Specifica file {0}"; private const string FMP_ToStringSize = "Dimensione iniziale: {0}";Imports System.ComponentModel Imports System.Text Imports System.Xml.Serialization Imports TAndT.Base Imports TAndT.Base.Entities Imports TAndT.Base.Xml Namespace Entities <Serializable(), XmlRoot(Namespace:="http://www.visual-basic.it")> _ Public Class FileSpecification Implements IEntity Public Const FLD_FileName As String = "FileName" Public Const FLD_Growth As String = "Growth" Public Const FLD_MaxSize As String = "MaxSize" Public Const FLD_Name As String = "Name" Public Const FLD_Size As String = "Size" Private Const FMP_ToSqlFileName As String = "FILENAME '{0}', " Private Const FMP_ToSqlGrowth As String = "GROWTH = {0} )" Private Const FMP_ToSqlMaxSize As String = "MAXSIZE = {0}, " Private Const FMP_ToSqlName As String = "( NAME = {0}, " Private Const FMP_ToSqlSize As String = "SIZE = {0}, " Private Const FMP_ToStringFileName As String = "Path: {0}" Private Const FMP_ToStringGrowth As String = "Valore di crescita automatica: {0}" Private Const FMP_ToStringMaxSize As String = "Dimensione massima ammessa: {0}" Private Const FMP_ToStringName As String = "Specifica file {0}" Private Const FMP_ToStringSize As String = "Dimensione iniziale: {0}"La struttura base della classe, e le costanti. Abbiamo indicato l'attributo Serializable, e l'attributo XmlRoot con il namespace per rendere univoca la serializzazione della classe.
La classe implementa l'interfaccia IEntity, quindi ci saranno proprietà e metodi ad essa legati.Oltre alla solita variabile per le eccezioni con il nome della classe, abbiamo predisposto una serie di costanti con i nomi delle proprietà, con prefisso FLD e una serie di Format pattern (FMP) che ci forniscono il necessario per formattare l'output del metodo ToString personalizzato e del metodo ToSqlString per la generazione del comando SQL di cui questa classe è parte.
private string mFileName; private DbFileGrowth mGrowth; private DbFileMaxSize mMaxSize; private string mName; private DbFileSize mSize;Private mFileName As String Private mGrowth As DbFileGrowth Private mMaxSize As DbFileMaxSize Private mName As String Private mSize As DbFileSizeI campi della classe, che rappresentano i parametri di una FileSpecification per il comando create database, quindi nome logico, nome del file, dimensione iniziale, dimensione massima e crescita automatica.
Omettiamo di mostrare le proprietà corrispondenti, passando direttamente ai costruttori:public FileSpecification(string pName, string pFileName) { this.Name = pName; this.FileName = pFileName; InitFileSize(); } public FileSpecification(string pName, string pFileName, DbFileSize pSize, DbFileMaxSize pMaxSize, DbFileGrowth pGrowth) { this.Name = pName; this.FileName = pFileName; this.Size = pSize; this.MaxSize = pMaxSize; this.Growth = pGrowth; } private FileSpecification() { this.Name = string.Empty; this.FileName = string.Empty; InitFileSize(); }Private Sub New() Me.Name = String.Empty Me.FileName = String.Empty InitFileSize() End Sub Public Sub New(ByVal name As String, ByVal fileName As String) Me.Name = name Me.FileName = fileName InitFileSize() End Sub Public Sub New(ByVal name As String, ByVal fileName As String, ByVal size As DbFileSize, _ ByVal maxSize As DbFileMaxSize, ByVal growth As DbFileGrowth) Me.Name = name Me.FileName = fileName Me.Size = size Me.MaxSize = maxSize Me.Growth = growth End SubI costruttori della classe sono un po' particolari, infatti ce ne sono tre, il costruttore di base senza parametro, richiesto dal fatto che la classe è serializzabile, inizializza i campi della classe con i valori di default ed è privato. Ricordiamo che le dimensioni di base sono state definite nelle classi di ciascuna delle dimensioni oppure, ma potrebbero essere inserite sul file di configurazione applicazione.
Il secondo ed il terzo costruttore invece sono parametrici e pubblici. In questo modo, obblighiamo chi utilizza la classe a specificare almeno i parametri necessari e sufficienti a costruire una classe valida.public int CompareTo(object obj) { int ret = -1; try { if (obj is FileSpecification) { FileSpecification val = (FileSpecification)obj; int comparator = CompareHelper.StringComparer(this.Name, val.Name); if (comparator == 0) { comparator = CompareHelper.StringComparer(this.FileName, val.FileName); if (comparator == 0) { comparator = this.Size.CompareTo(val.Size); if (comparator == 0) { comparator = this.MaxSize.CompareTo(val.MaxSize); if (comparator == 0) { comparator = this.Growth.CompareTo(val.Growth); } } } } ret = comparator; } } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } return (ret); }Public Function CompareTo(ByVal obj As Object) As Integer _ Implements System.IComparable.CompareTo Dim ret As Integer = -1 Try If TypeOf obj Is FileSpecification Then Dim val As FileSpecification = DirectCast(obj, FileSpecification) Dim comparator As Integer = CompareHelper.StringComparer(Me.Name, val.Name) If comparator = 0 Then comparator = CompareHelper.StringComparer(Me.FileName, val.FileName) If comparator = 0 Then comparator = Me.Size.CompareTo(val.Size) If comparator = 0 Then comparator = Me.MaxSize.CompareTo(val.MaxSize) If comparator = 0 Then comparator = Me.Growth.CompareTo(val.Growth) End If End If End If End If ret = comparator End If Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try Return ret End Functionil metodo di comparazione compara tutti i componenti della nostra classe FileSpecification, pertanto due FileSpecification sono uguali quando tutti i loro componenti sono uguali.
public bool IsValid() { try { return (!StringHelper.IsNullOrTrimEmpty(this.Name) && !StringHelper.IsNullOrTrimEmpty(this.FileName) && Size.IsValid() && MaxSize.IsValid() && Growth.IsValid()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function IsValid() As Boolean Implements IEntity.IsValid Try Return Not StringHelper.IsNullOrTrimEmpty(Me.Name) AndAlso _ Not StringHelper.IsNullOrTrimEmpty(Me.FileName) AndAlso _ Size.IsValid AndAlso MaxSize.IsValid AndAlso Growth.IsValid Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionIl metodo di validazione utilizza il metodo helper che abbiamo creato all'inizio di questo articolo per la validazione dei campi stringa, e usa le funzioni di validazione specifiche per le classi dimensione discusse nell'articolo precedente.
public override string ToString() { try { StringBuilder sb = new StringBuilder(); sb.AppendFormat(FMP_ToStringName, this.Name); sb.AppendLine(); sb.AppendFormat(FMP_ToStringFileName, this.FileName); sb.AppendLine(); sb.AppendFormat(FMP_ToStringSize, this.Size); sb.AppendLine(); sb.AppendFormat(FMP_ToStringMaxSize, this.MaxSize); sb.AppendLine(); sb.AppendFormat(FMP_ToStringGrowth, this.Growth); return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex); } }Public Overrides Function ToString() As String Try Dim sb As New StringBuilder sb.AppendFormat(FMP_ToStringName, Me.Name) sb.AppendLine() sb.AppendFormat(FMP_ToStringFileName, Me.FileName) sb.AppendLine() sb.AppendFormat(FMP_ToStringSize, Me.Size) sb.AppendLine() sb.AppendFormat(FMP_ToStringMaxSize, Me.MaxSize) sb.AppendLine() sb.AppendFormat(FMP_ToStringGrowth, Me.Growth) Return sb.ToString Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod.Name _ + ": " + ex.Message, ex) End Try End FunctionL'override del metodo ToString, che ci permette di visualizzare in modo presentabile il contenuto della classe.
public string ToSqlString() { try { StringBuilder sb = new StringBuilder(); sb.AppendFormat(FMP_ToSqlName, this.Name); sb.AppendLine(); sb.AppendFormat(FMP_ToSqlFileName, this.FileName); sb.AppendLine(); sb.AppendFormat(FMP_ToSqlSize, this.Size); sb.AppendLine(); sb.AppendFormat(FMP_ToSqlMaxSize, this.MaxSize); sb.AppendLine(); sb.AppendFormat(FMP_ToSqlGrowth, this.Growth); return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex); } }Public Function ToSqlString() Try Dim sb As New StringBuilder sb.AppendFormat(FMP_ToSqlName, Me.Name) sb.AppendLine() sb.AppendFormat(FMP_ToSqlFileName, Me.FileName) sb.AppendLine() sb.AppendFormat(FMP_ToSqlSize, Me.Size) sb.AppendLine() sb.AppendFormat(FMP_ToSqlMaxSize, Me.MaxSize) sb.AppendLine() sb.AppendFormat(FMP_ToSqlGrowth, Me.Growth) Return sb.ToString() Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End Functionil metodo che genera la stringa SQL per la specifica file, da utilizzare per la composizione del Create Database.
La classe FileSpecifications
Aggiungiamo quindi alla libreria TAndT.Data una cartella Collections, nella quale creiamo la classe FileSpecifications.
Questa collezione non presenta nulla di nuovo rispetto alle altre già presentate, pertanto ne mostrerò solo una minima parte, ovvero ciò che c'è di peculiare:public bool IsValid() { try { bool countValid = false; bool elementValids = false; if (this.Count > 0) { countValid = true; int validItems = 0; foreach (FileSpecification item in this) { if (item.IsValid()) validItems++; } if (this.Count == validItems) { elementValids = true; } } return (countValid && elementValids); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function IsValid() As Boolean Try Dim countValid As Boolean = False Dim elementValids As Boolean = False If Me.Count > 0 Then countValid = True Dim validItems = 0 For Each item As FileSpecification In Me If item.IsValid Then validItems += 1 Next If Me.Count = validItems Then elementValids = True End If End If Return countValid AndAlso elementValids Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionLa validazione della collezione, che controlla che ci sia almeno un elemento e che ogni elemento che contiene sia valido.
public string ToSqlString() { try { StringBuilder sb = new StringBuilder(); bool primo = true; foreach (FileSpecification item in this) { if (!primo) { sb.AppendLine(", "); } sb.AppendLine(item.ToSqlString()); primo = false; } return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function ToSqlString() As String Try Dim sb As New StringBuilder Dim primo As Boolean = True For Each item As FileSpecification In Me If Not primo Then sb.AppendLine(", ") End If sb.AppendLine(item.ToSqlString) primo = False Next Return sb.ToString Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionLa generazione della stringa SQL per la collezione, ottenuta concatenando le stringhe SQL degli elementi, separate da virgole.
La classe DataFileGroup
Il filegroup di un database è un gruppo logico composto da uno o più file che contengono una parte delle tabelle (o tutte) di un database, siano esse tabelle di sistema o tabelle utente. Ogni database deve averne almeno uno, il cui nome è PRIMARY.
Anche questo oggetto, come la specifica di file, è un entità, che contiene al suo interno altri oggetti e, così come la FileSpecification conteneva gli oggetti DbFileSize, DBFileMaxSize, DbFileGrowth, così il DataFileGroup contiene anche una collection di tipo FileSpecification.
Se vi sembra di star giocando con le Matrioske oppure con le scatole cinesi, ebbene: benvenuti in OOP!Anche questa classe, da creare nella cartella TAndT.Data.Entities, è simile a quelle già presentate, pertanto ci limiteremo a spiegare le differenze:
private FileSpecifications mFileSpecs; private bool mIsPrimary; private string mName;Private mFileSpecs As FileSpecifications Private mIsPrimary As Boolean Private mName As StringI campi della nostra classe: la lista dei file contenuti nel filegroup, un flag per indicarci che questo è il PRIMARY filegroup, il nome del filegroup. Ciascuno di questi campi è esposto con una proprietà pubblica uguale a tutte le property delle entità già spiegate, quindi le omettiamo.
private DataFileGroup() { this.mName = string.Empty; this.mFileSpecs = new FileSpecifications(); } public DataFileGroup(string pName) { this.mName = pName; this.mFileSpecs = new FileSpecifications(); }Private Sub New() Me.mName = String.Empty Me.mFileSpecs = New FileSpecifications End Sub Public Sub New(ByVal name As String) Me.mName = name Me.mFileSpecs = New FileSpecifications End SubI costruttori; anche in questo caso ne abbiamo uno privato per la serializzazione e l'altro pubblico e parametrico perché è necessario che il filegroup abbia un nome.
public int CompareTo(object obj) { int ret = -1; try { if (obj is DataFileGroup) { DataFileGroup val = (DataFileGroup)obj; int comparator = CompareHelper.StringComparer(this.Name, val.Name); if (comparator == 0) { comparator = this.FileSpecs.CompareTo(val.FileSpecs); if (comparator == 0) { comparator = this.IsPrimary.CompareTo(val.IsPrimary); } } ret = comparator; } } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } return (ret); }Public Function CompareTo(ByVal obj As Object) As Integer _ Implements System.IComparable.CompareTo Dim ret As Integer = -1 Try If TypeOf obj Is DataFileGroup Then Dim val As DataFileGroup = DirectCast(obj, DataFileGroup) Dim comparator As Integer = CompareHelper.StringComparer(Me.Name, val.Name) If comparator = 0 Then comparator = Me.FileSpecs.CompareTo(val.FileSpecs) If comparator = 0 Then comparator = Me.IsPrimary.CompareTo(val.IsPrimary) End If End If ret = comparator End If Catch ex As Exception Throw New ApplicationException("\'a0" + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try Return ret End Functionil metodo di comparazione, che (come gli altri analoghi) compara fra loro tutti gli elementi dell'entità.
public bool IsValid() { try { return (!StringHelper.IsNullOrTrimEmpty(this.Name) && FileSpecs.IsValid()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function IsValid() As Boolean Implements IEntity.IsValid Try Return Not StringHelper.IsNullOrTrimEmpty(Me.Name) AndAlso FileSpecs.IsValid Catch ex As Exception Throw New ApplicationException("\'a0" + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End Functionil metodo di validazione, che verifica che ci sia il nome del filegroup e che la collezione di FileSpecification sia a sua volta valida.
public string ToSqlString() { try { if (!this.FileSpecs.IsValid()) { throw new InvalidFileSpecificationsException( "La specifica dei file del filegroup non è valida, " + "impossibile generare la stringa SQL per il filegroup."); } StringBuilder sb = new StringBuilder(); if (this.IsPrimary) { sb.AppendLine(TXT_ToSqlStringPrimaryFileGroup); } else { sb.AppendFormat(FMP_ToSqlStringFilegroupName, this.Name); sb.AppendLine(); } sb.Append(this.FileSpecs.ToSqlString()); return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex); } }Public Function ToSqlString() Try If Not Me.FileSpecs.IsValid Then Throw New InvalidFileSpecificationsException( _ "La specifica dei file del filegroup non è valida, " & _ "impossibile generare la stringa SQL per il filegroup.") End If Dim sb As New StringBuilder If Me.IsPrimary Then sb.AppendLine(TXT_ToSqlStringPrimaryFileGroup) Else sb.AppendFormat(FMP_ToSqlStringFilegroupName, Me.Name) sb.AppendLine() End If sb.Append(Me.FileSpecs.ToString) Return sb.ToString() Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End Functionil metodo di generazione della stringa SQL che controlla ed eventualmente genera una eccezione se la classe non è valida, oppure genera la stringa SQL necessaria per il Create Database.
public override string ToString() { try { StringBuilder sb = new StringBuilder(); sb.AppendFormat(FMP_ToStringName, this.Name); sb.AppendLine(); sb.AppendLine(TXT_ToStringFileList); sb.AppendLine(this.FileSpecs.ToString()); return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex); } }Public Overrides Function ToString() As String Try Dim sb As New StringBuilder sb.AppendFormat(FMP_ToStringName, Me.Name) sb.AppendLine() sb.AppendLine(TXT_ToStringFileList) sb.AppendLine(Me.FileSpecs.ToString) Return sb.ToString Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod.Name _ + ": " + ex.Message, ex) End Try End Functionil metodo ToString, che genera una stringa in formato adatto alla visualizzazione. Ovviamente, tutte le costanti per le stringhe usate nella classe, sono definite all'interno della classe stessa oppure, se si necessita di gestione multilingua, possono essere riportate nelle risorse del progetto.
La classe DataFileGroups
In un database è possibile definire più di un filegroup, pertanto abbiamo bisogno di una collezione di DataFileGroup, quindi, creiamo una nuova collezione, simile a quella usata per i filegroup.
Anche questa collezione non aggiunge nulla alle precedenti già spiegate, pertanto ci limiteremo a mostrarvi un metodo, il metodo ToSqlString, l'unico peculiare, il quale fa uso di una eccezione personalizzata, pertanto creiamo prima quest'ultima, aggiungendo la classe InvalidFileSpecificationsException alle Entities della TAndT.Data:namespace TAndT.Data.Entities { public class InvalidFileSpecificationsException : System.ApplicationExceptionNamespace Entities Public Class InvalidFileSpecificationsException Inherits System.ApplicationExceptionEd ecco il metodo:
public string ToSqlString() { try { StringBuilder sb = new StringBuilder(); bool primo = true; foreach (DataFileGroup item in this) { if (!primo) { sb.AppendLine(", "); } sb.AppendLine(item.ToSqlString()); primo = false; } return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function ToSqlString() As String Try Dim sb As New StringBuilder Dim primo As Boolean = True For Each item As DataFileGroup In Me If Not primo Then sb.AppendLine(", ") End If sb.AppendLine(item.ToSqlString) primo = False Next Return sb.ToString Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionCome possiamo vedere, anche se peculiare, il metodo non fa nulla di nuovo, se non concatenare il risultato della ToSqlString degli elementi della collezione.
La classe DatabaseGenerator
Nonostante sia la classe più interessante del gruppo di classi necessarie a generare un Database su SQL Server, in realtà non ha nulla di particolarmente interessante al suo interno: infatti, anch'essa è un'Entity (cioè una classe che creiamo in TAndT.Data.Entities) che contiene al suo interno gli oggetti finora costruiti, un altra Matrioska. In altre parole, abbiamo distribuito le cose interessanti negli oggetti già definiti finora.Questa classe è la più interessante perché contiene i dati necessari alla generazione di un database ed i metodi per farlo.
Per questa puntata, definiremo il necessario a far funzionare l'entità, nella prossima puntata, finalmente, genereremo il database.using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.Xml.Serialization; using TAndT.Data.Collections; using TAndT.Base; using TAndT.Base.Entities; using TAndT.Base.Xml; namespace TAndT.Data.Entities { [Serializable, XmlRoot(Namespace = "http://www.visual-basic.it")] public class DatabaseGenerator : IEntity { private const string FLD_Collation = "Collation"; private const string FLD_DataFiles = "DataFiles"; private const string FLD_LogFileSpec = "LogFileSpec"; private const string FLD_DatabaseName = "DatabaseName";Imports System.ComponentModel Imports System.Text Imports System.Xml.Serialization Imports TAndT.Base Imports TAndT.Base.Entities Imports TAndT.Base.Xml Imports TAndT.Data.Collections Namespace Entities <Serializable(), XmlRoot(Namespace:="http://www.visual-basic.it")> _ Public Class DatabaseGenerator Implements IEntity Public Const FLD_Collation As String = "Collation" Public Const FLD_DataFiles As String = "DataFiles" Public Const FLD_LogFileSpec As String = "LogFileSpec" Public Const FLD_DatabaseName As String = "DatabaseName"La struttura di base e le costanti con i nomi dei campi della classe.
string mCollation; DataFileGroups mDataFiles; FileSpecifications mLogFileSpec; string mDatabaseName;Private mCollation As String Private mDataFiles As DataFileGroups Private mLogFileSpec As FileSpecifications Private mDatabaseName As StringI campi della classe, che, come possiamo vedere, contiene le collezioni precedentemente definite, dando origine ad un'altra matrioska OOP. Ovviamente ogni campo è esposto tramite la sua property (che omettiamo).
private DatabaseGenerator() { DatabaseGeneratorInit(); } public DatabaseGenerator(string pDatabaseName) { DatabaseGeneratorInit(); this.DatabaseName = pDatabaseName; }Private Sub New() DatabaseGeneratorInit() End Sub Public Sub New(ByVal databaseName As String) DatabaseGeneratorInit() Me.DatabaseName = databaseName End SubI costruttori: quello privato per la serializzazione, quello pubblico per l'uso, cui abbiamo messo un solo parametro, il nome del database, perché per generare un database, se ben ricordate l'articolo precedente, ci basta quello, in realtà.
Entrambi usano un metodo di inizializzazione:private void DatabaseGeneratorInit() { this.mCollation = string.Empty; this.mDataFiles = new DataFileGroups(); this.mLogFileSpec = new FileSpecifications(); this.mDatabaseName = string.Empty; }Private Sub DatabaseGeneratorInit() Me.mCollation = String.Empty Me.mDataFiles = New DataFileGroups() Me.mLogFileSpec = New FileSpecifications() Me.mDatabaseName = String.Empty End Subil metodo di inizializzazione dei campi usato dai due costruttori.
public int CompareTo(object obj) { int ret = -1; try { if (obj is DatabaseGenerator) { DatabaseGenerator val = (DatabaseGenerator)obj; int comparator = CompareHelper.StringComparer(this.DatabaseName, val.DatabaseName); if (comparator == 0) { comparator = this.DataFiles.CompareTo(val.DataFiles); if (comparator == 0) { comparator = this.LogFileSpec.CompareTo(val.LogFileSpec); if (comparator == 0) { comparator = CompareHelper.StringComparer(this.Collation, val.Collation); } } } ret = comparator; } } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } return (ret); }Public Function CompareTo(ByVal obj As Object) As Integer _ Implements System.IComparable.CompareTo Dim ret As Integer = -1 Try If TypeOf obj Is DatabaseGenerator Then Dim val As DatabaseGenerator = DirectCast(obj, DatabaseGenerator) Dim comparator As Integer = CompareHelper.StringComparer(Me.DatabaseName, val.DatabaseName) If comparator = 0 Then comparator = Me.DataFiles.CompareTo(val.DataFiles) If comparator = 0 Then comparator = Me.LogFileSpec.CompareTo(val.LogFileSpec) If comparator = 0 Then comparator = CompareHelper.StringComparer(Me.Collation, val.Collation) End If End If End If ret = comparator End If Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try Return ret End FunctionIl metodo di comparazione, che utilizza i campi della classe per stabilire se due DatabaseGenerator sono uguali.
public bool IsValid() { try { return (!StringHelper.IsNullOrTrimEmpty(this.DatabaseName)); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function IsValid() As Boolean Implements IEntity.IsValid Try Return Not StringHelper.IsNullOrTrimEmpty(Me.DatabaseName) Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionIl metodo di validazione; anche in questo caso, basta che ci sia il nome database, il resto è opzionale.
public override string ToString() { try { StringBuilder sb = new StringBuilder(); sb.AppendLine("Dati generazione database:"); sb.AppendFormat("Nome: {0}", this.DatabaseName); sb.AppendLine(); sb.AppendFormat("Collation: {0}", this.Collation); sb.AppendLine(this.DataFiles.ToString()); sb.AppendLine(this.LogFileSpec.ToString()); return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex); } }Public Overrides Function ToString() As String Try Dim sb As New StringBuilder sb.AppendLine("Dati generazione database:") sb.AppendFormat("Nome: {0}", Me.DatabaseName) sb.AppendLine() sb.AppendFormat("Collation: {0}", Me.Collation) sb.AppendLine(Me.DataFiles.ToString()) sb.AppendLine(Me.LogFileSpec.ToString()) Return sb.ToString Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod.Name _ + ": " + ex.Message, ex) End Try End FunctionIl metodo ToString, che compone in modo adatto alla visualizzazione i contenuti dei campi della classe.
public string ToSqlString() { try { StringBuilder sb = new StringBuilder(); sb.AppendLine("CREATE DATABASE "); sb.Append("\t"); sb.AppendLine(this.DatabaseName); if (this.DataFiles.IsValid()) { sb.AppendLine("\tON"); sb.AppendLine(this.DataFiles.ToSqlString()); } if (this.LogFileSpec.IsValid()) { sb.AppendLine("\tLOG ON"); sb.AppendLine(this.LogFileSpec.ToSqlString()); } if (!StringHelper.IsNullOrTrimEmpty(this.Collation)) { sb.AppendLine("\t"); sb.AppendFormat("COLLATE {0}", this.Collation); sb.AppendLine(); } return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function ToSqlString() As String Dim tab As Char = Convert.ToChar(9) Try Dim sb As New StringBuilder sb.AppendLine("CREATE DATABASE ") sb.Append(tab) sb.AppendLine(Me.DatabaseName) If Me.DataFiles.IsValid Then sb.AppendLine(Me.DataFiles.ToSqlString()) End If If Me.LogFileSpec.IsValid Then sb.Append(tab) sb.AppendLine("LOG ON") sb.AppendLine(Me.LogFileSpec.ToSqlString()) End If If Not StringHelper.IsNullOrTrimEmpty(Me.Collation) Then sb.AppendLine(tab) sb.AppendFormat("COLLATE {0}", Me.Collation) sb.AppendLine() End If Return sb.ToString() Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionIl metodo ToSqlString, l'unico interessante, che compone la nostra stringa SQL di Create Database, utilizzando i campi del DatabaseGenerator e le funzioni di validazione per decidere se includere oppure no un elemento della classe. In questo modo, la stringa viene composta dinamicamente con i dati che chi ha deciso di generare il database è in grado di fornire.
Arrivederci alla prossima puntata
Per questa puntata ci fermiamo qui. Questa è una puntata lunga e noiosa, lo ammettiamo, ma nella prossima vedremo l'uso pratico delle classi finora sviluppate e ci sarà probabilmente un po' più di movimento e divertimento per le cose nuove.Se vogliamo parlare di Filosofia del lavoro del programmatore, possiamo considerare come vi siano momenti stimolanti, quando si deve definire un nuovo tipo di oggetto e le sue peculiarità, e momenti necessari ma noiosi, quando bisogna implementare molti oggetti correlati fra loro ma tutti di tipi noti, come le Matrioske di questo articolo.
Come al solito, il codice fin qui prodotto è scaricabile dall'Area Download.
(nota: il codice è 'sempre' aggiornato, con eventuali correzioni al codice precedente, non citate nell'articolo).
Potete scrivere Feedback (commenti, critiche, suggerimenti, correzioni) sul blog di Sabrina.