Guarda! Senza mani! - Nona parte
a cura di Sabrina Cosolo e Diego Cattaruzza (requisiti: conoscenza intermedia di Sql Server e di .Net)

Premessa
In questa puntata, scriveremo tutta l'interfaccia utente che ci permette di usare e quindi testare le classi che abbiamo disegnato per la gestione della generazione del database.
L'interfaccia è piuttosto complessa: deve permetterci infatti di inserire dati strutturati di vario tipo.
Questa complessità ci serve come scusa per spiegare tutta una serie di concetti, che speriamo troverete utili per lo sviluppo delle vostre applicazioni. Vi anticipiamo gli argomenti, chissà che non vi ispiri a proseguire la lettura
Ecco cosa creeremo in questa puntata:

Qualche correzione
Prima di iniziare con le cose nuove, facciamo alcune piccole modifiche a ciò che avevamo fatto fin qui, per migliorare alcune cose e correggere alcuni problemi che sono stati scoperti sviluppando e testando il codice di questa puntata. Non ha senso che perdiamo tempo a spiegarvi come sono stati trovati, perché il debug non è centrale in questi articoli, ma se avete la versione 8.0 potete provare a fare la parte di sviluppo e poi debuggare e capire come abbiamo trovato i problemi.

Classe TAndT.Data.Collections.DataFileGroups:

E' stata modificato il metodo ToSqlString nel seguente modo:

    public string ToSqlString()
    {
      try
      {
        StringBuilder sb = new StringBuilder();
        bool primo = true;
        foreach (DataFileGroup item in this)
        {
          if (!primo)
          {
            sb.AppendLine(", ");
          }
          //sb.AppendLine(item.ToSqlString()); <<<<<<<<<
          sb.Append(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) <<<<<<<<<
          sb.Append(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 Function

Abbiamo modificato l'AppendLine in Append per evitare che le eventuali virgole fra un elemento e il successivo si trovino su una riga da sole, solo una questione estetica, ma è importante anche per la leggibilità del codice SQL generato.

Classe TAndT.Data.Collections.FileSpecifications

Anche in questa classe modifichiamo il metodo ToSqlString nello stesso modo per lo stesso motivo:

    public string ToSqlString()
    {
      try
      {
        StringBuilder sb = new StringBuilder();
        bool primo = true;
        foreach (FileSpecification item in this)
        {
          if (!primo)
          {
            sb.AppendLine(", ");
          }
          //sb.AppendLine(item.ToSqlString()); <<<<<<<<<
          sb.Append(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) <<<<<<<<<
          sb.Append(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 Function

Classe TAndT.Data.Entities.DatabaseGenerator:

Modifichiamo il ToSqlString perché vi avevamo inserito una riga errata

    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
      Try
        Dim sb As New StringBuilder
        sb.AppendLine("CREATE DATABASE ")
        sb.Append("\t")
        sb.AppendLine(Me.DatabaseName)
        If Me.DataFiles.IsValid Then
          'sb.AppendLine("\tON")                    <<<<<<<<<<<<<<<
          sb.AppendLine(Me.DataFiles.ToSqlString())
        End If
        If Me.LogFileSpec.IsValid Then
          sb.AppendLine("\tLOG ON")
          sb.AppendLine(Me.LogFileSpec.ToSqlString())
        End If
        If Not StringHelper.IsNullOrTrimEmpty(Me.Collation) Then
          sb.AppendLine("\t")
          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 Function

In questo caso abbiamo tolto la riga evidenziata, perché la generazione delle stringhe dei filegroups si occupa di generare anche il pezzo che era inserito in quel punto.

Classe TAndT.Data.Entities.DbFileMaxSize:

Abbiamo modificato l'IsValid della classe per fare in modo che funzioni correttamente quando si seleziona la modalità Unlimited

    public override bool IsValid()
    {
      try
      {
        if (this.SizeUmi == VAL_SpecialUmiUnlimited)
        {
          return true;
        }
        else
        {
          return (this.SizeValue > 0 && !StringHelper.IsNullOrTrimEmpty(this.SizeUmi) &&
            this.SizeUmi != VAL_SpecialUmiPercent);
        }
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
    Public Overrides Function IsValid() As Boolean
      Try
        If Me.SizeUmi = VAL_SpecialUmiUnlimited Then
          Return True
        Else
          Return Me.SizeValue > 0 AndAlso StringHelper.IsNullOrTrimEmpty(Me.SizeUmi) _
                                  AndAlso Not Me.SizeUmi = VAL_SpecialUmiPercent
        End If

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                             + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try
    End Function

Classe TAndT.Data.Entities.FileSpecification

Abbiamo modificato due costanti

    private const string FMP_ToSqlFileName = "FILENAME = '{0}', ";
    private const string FMP_ToSqlGrowth = "FILEGROWTH = {0} )";
    Private Const FMP_ToSqlFileName As String = "FILENAME = '{0}', "
    Private Const FMP_ToSqlGrowth As String = "FILEGROWTH = {0} )"

La prima mancava del segno = dopo la parola FileName
La seconda, indicava GROWTH invece di FILEGROWTH.

Abbiamo modificato il metodo ToSqlString

    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.AppendFormat(FMP_ToSqlSize, this.Size.ToSqlString());
        sb.AppendLine();
        //sb.AppendFormat(FMP_ToSqlMaxSize, this.MaxSize);  <<<<<<
        sb.AppendFormat(FMP_ToSqlMaxSize, this.MaxSize.ToSqlString());
        sb.AppendLine();
        //sb.AppendFormat(FMP_ToSqlGrowth, this.Growth);    <<<<<<
        sb.AppendFormat(FMP_ToSqlGrowth, this.Growth.ToSqlString());
        return (sb.ToString());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
      }
    }
    Public Function ToSqlString() As String
      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.AppendFormat(FMP_ToSqlSize, Me.Size.ToSqlString)
        sb.AppendLine()
        'sb.AppendFormat(FMP_ToSqlMaxSize, Me.MaxSize)          <<<<<<
        sb.AppendFormat(FMP_ToSqlMaxSize, Me.MaxSize.ToSqlString)
        sb.AppendLine()
        'sb.AppendFormat(FMP_ToSqlGrowth, Me.Growth)            <<<<<<
        sb.AppendFormat(FMP_ToSqlGrowth, Me.Growth.ToSqlString)
        Return sb.ToString()

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                               + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try

    End Function

La modifica è stata effettuata sulle righe contrassegnate perché le tre dimensioni erano formattate indicando solamente this.Size o this.MaxSize, quindi il metodo AppendFormat eseguiva automaticamente il ToString della classe per ottenerne la stringa, mentre noi abbiamo predisposto ToSqlString come metodo per formattare correttamente la classe in una stringa SQL. Abbiamo quindi sostituito la chiamata corretta per produrre la stringa SQL.

Classe TAndT.UsingSqlServer.UsingSqlServerConfig

    public static string[] SizeUmiList
    {
      get
      {
        return AppSettings[STT_APPDbSizeUmi].Value.Split(
                           new char[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
      }
      set
      {
        AppSettings[STT_APPDbSizeUmi].Value = String.Join(";", value);
      }
    }
		
    public static string[] MaxSizeUmiList
    {
      get
      {
        return AppSettings[STT_APPDbMaxSizeUmi].Value.Split(
                           new char[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
      }
      set
      {
        AppSettings[STT_APPDbMaxSizeUmi].Value = String.Join(";", value);
      }
    }

     public static string[] GrowthUmiList
    {
      get
      {
        return AppSettings[STT_APPDbGrowthUmi].Value.Split(
                           new char[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
      }
      set
      {
        AppSettings[STT_APPDbGrowthUmi].Value = String.Join(";", value);
      }
    }
  Public Shared Property SizeUmiList() As String()
    Get
      Return AppSettings(STT_APPDbSizeUmi).Value.Split( _
                         New Char() {";"c, " "c}, StringSplitOptions.RemoveEmptyEntries)
    End Get
    Set(ByVal value As String())
      AppSettings(STT_APPDbSizeUmi).Value = String.Join(";", value)
    End Set
  End Property

  Public Shared Property MaxSizeUmiList() As String()
    Get
      Return AppSettings(STT_APPDbMaxSizeUmi).Value.Split( _
                         New Char() {";"c, " "c}, StringSplitOptions.RemoveEmptyEntries)
    End Get
    Set(ByVal value As String())
      AppSettings(STT_APPDbMaxSizeUmi).Value = String.Join(";", value)
    End Set
  End Property

  Public Shared Property GrowthUmiList() As String()
    Get
      Return (AppSettings(STT_APPDbGrowthUmi).Value.Split( _
                          New Char() {";"c, " "c}, StringSplitOptions.RemoveEmptyEntries))
    End Get
    Set(ByVal value As String())
      AppSettings(STT_APPDbGrowthUmi).Value = String.Join(";", value)
    End Set
  End Property

Abbiamo modificato il tipo delle StringCollection in array di String, poiché il tipo StringCollection creava più complicazioni che facilitazioni.

Qualche modifica
Terminate le correzioni, passiamo alle cose interessanti, le cose nuove: per prima cosa facciamo una modifica al metodo VediSeIlFormEAperto, perché, seppure non in questa puntata, ci servirà in seguito: la modifica che faremo servirà a permetterci di verificare se una form è aperta sia essa MDIChild o Owned.

Classe TAndT.UI.Forms.FormHelper

    public static Form VediSeIlFormEAperto(string pName, Form pParent, bool pIsMdi, bool pIsOwner)
    {
      try
      {
        Form retForm = null;
        if (pIsMdi)
        {
          retForm = CheckFormList(pParent.MdiChildren, pName);
        }
        else
        {
          if (pIsOwner)
          {
            retForm = CheckFormList(pParent.OwnedForms, pName);
          }
        }
        return (retForm);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }

    private static Form CheckFormList(Form[] pFormList, string pName )
    {
      try
      {
        Form retForm = null;
        foreach (Form child in pFormList)
        {
          if (child.Name == pName)
          {
            retForm = child;
            break;
          }
        }
        return (retForm);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
    Public Shared Function VediSeIlFormEAperto(ByVal name As String, ByVal parent As Form, _
                                           ByVal isMdi As Boolean, ByVal isOwned As Boolean) As Form
      Try
        Dim retForm As Form = Nothing
        If isMdi Then
          retForm = CheckFormList(parent.MdiChildren, name)
        Else
          If isOwned Then
            retForm = CheckFormList(parent.OwnedForms, name)
          End If
        End If
        Return retForm

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try
    End Function

    Private Shared Function CheckFormList(ByVal formList As Form(), ByVal name As String) As Form
      Try
        Dim retForm As Form = Nothing
        For Each child As Form In formList
          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

In questa modifica, abbiamo spostato il codice precedente del metodo VediSeIlFormEAperto in un nuovo metodo CheckFormList, che riceve una lista di form e il nome della form da controllare, mentre, del metodo VediSeIlFormEAperto, abbiamo cambiato i parametri aggiungendo due flag per indicare se si tratta di una richiesta per una MDIChild oppure per una Owned form, in base ad essi usiamo il metodo CheckFormList. Da notare inoltre che, in una classe statica, anche i metodi privati devono essere statici, se no non vengono visti nemmeno all'interno della stessa classe (questo nel caso a qualcuno sembri inutile qualificare statico un metodo privato).

Come conseguenza di questa modifica, nella classe FrmConfig verrà modificata la chiamata di VediSeIlFormEAperto, allo stesso modo saranno modificate le chiamate alla stessa funzione nel FrmMain.

    public static void OpenMe(Icon pIcon, Form pMDI)
    {
      try
      {
        FrmConfig frm = (FrmConfig)FormHelper.VediSeIlFormEAperto("FrmConfig", pMDI, true, false);
        ...
  Public Shared Sub OpenMe(ByVal icon As Icon, ByVal mdi As Form)
    Try
      Dim frm As FrmConfig = DirectCast(FormHelper.VediSeIlFormEAperto("FrmConfig", mdi, True, _
                                        False), FrmConfig)
      ...

Ancora un Setting
Per la gestione della creazione dei database, ci serve ancora una cosa, un Setting che contenga la cartella base di destinazione dei file di database, da proporre automaticamente per la creazione. Pertanto modifichiamo la classe UsingSqlServerConfig.

Classe UsingSqlServerConfig
La prima modifica è l'aggiunta di due costanti: il nome di un nuovo setting e il suo valore di default, che useremo per memorizzare la cartella da proporre per la generazione dei file di database.

    private const string STT_APPDbFilesFolder = "DbFilesFolder";

    private const string DEFAULT_DbFilesFolder = "c:\\sqldatadir";
  Private Const DEFAULT_DbFilesFolder As String = "c:\sqldatadir"

  Public Const STT_APPDbFilesFolder As String = "DbFilesFolder"

Modifichiamo il metodo LoadSettings, aggiungendo in fondo alla serie dei  Settings di applicazione la generazione del nuovo setting per la cartella standard per il database.

    public static void LoadSettings()
    {
      try
      {
        ...
        if (!AppSettings.ExistSetting(STT_APPDbFilesFolder))
        {
          AppSettings.Add(STT_APPDbFilesFolder, DEFAULT_DbFilesFolder);
          hasToBeSaved = true;
        }

        TAndTSettings.LoadSettings(mUserFileName, ref mUserSettings);
        ...
    }
  Public Shared Sub LoadSettings()
    Try
      ...
      If Not AppSettings.ExistSetting(STT_APPDbFilesFolder) Then
        AppSettings.Add(STT_APPDbFilesFolder, DEFAULT_DbFilesFolder)
        hasToBeSaved = True
      End If

      TAndTSettings.LoadSettings(mUserFileName, mUserSettings)
      ...
  End Sub

Aggiungiamo la proprietà per  esporre il nuovo Setting.

    public static string DbFilesFolder
    {
      get
      {
        return AppSettings[STT_APPDbFilesFolder].Value;
      }
      set
      {
        AppSettings[STT_APPDbFilesFolder].Value = value;
      }
    }
  Public Shared Property DbFilesFolder() As String
    Get
      Return AppSettings(STT_APPDbFilesFolder).Value
    End Get
    Set(ByVal value As String)
      AppSettings(STT_APPDbFilesFolder).Value = value
    End Set
  End Property

Nuovi menu nella classe FrmMain
Modifichiamo la nostra MDI per aggiungere le opzioni di menu necessarie a quanto svilupperemo in questa puntata.
Per prima cosa ne introduciamo due che ci permetteranno di testare due nuove Form generiche che metteremo in libreria.

Spostiamoci sul Menu della FrmMain e nel menu Helper Tests aggiungiamo due nuove opzioni:

Sul menu principale, aggiungiamo un nuovo menu  ed un sottomenu che chiamiamo:

Facciamo doppio click su ciascuna delle tre opzioni di menu per generare i rispettivi metodi di gestione dell'evento Click, ma non scriviamo ancora nulla nel codice, che implementeremo quando avremo prodotto ciò che i tre menu chiamano.

Nuove Form nel Progetto TAndT.UI
In questo progetto abbiamo già generato la classe FormHelper, con il metodo che verifica l'esistenza di un istanza di una form e ne recupera l'istanza aperta oppure ne genera una nuova.

Per poter creare l'interfaccia utente che ci permetta di generare un database, abbiamo bisogno di alcune form completamente nuove; fra queste, abbiamo individuato la necessità di utilizzare una form simile alla InputBox di VBA per acquisire un valore stringa, ed una form molto simile, ma in grado di farci selezionare un valore in una Combobox.
Considerato il fatto che due form simili potrebbero essere necessarie anche nei futuri sviluppi, abbiamo deciso di crearle come form generiche e di inserirle nella nostra libreria TAndT.UI.
Le due nuove form, sono le seguenti:

  1. FrmInputBox - Form per l'input di una stringa.
  2. FrmInputCombo - Form per l'input di un valore da combobox.

Prima di creare le nostre due form, apriamo il file Resources.resx del progetto TAndT.UI ed aggiungiamo le immagini per i due bottoni che ci serviranno per i tasti OK e Cancel, che si trovano nella cartella Resources del progetto a corredo.
Per aggiungerli, impostiamo Images e poi Add Resource> Add Existing File>, selezioniamo i file che saranno caricati sulle risorse come nell'immagine qui accanto.

Bottoni OK e CANCEL per dialog

La struttura della form InputboxLa classe FrmInputBox
Dal progetto TAndT.UI, facciamo click con il tasto destro e selezioniamo Add>Windows Form> Chiamiamo la form FrmInputBox e aggiungiamo i controlli e modifichiamo le loro proprietà come indicato qui sotto:

Disponiamo i controlli come nella figura e andiamo a guardare che cosa mettiamo nel codice della form:

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace TAndT.UI
{
  public partial class FrmInputBox : Form
  {
    private static readonly string mClassName = 
                                 System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;
Imports System
Imports System.ComponentModel
Imports System.Drawing
Imports System.Windows.Forms

Public Class FrmInputBox

  Private Shared ReadOnly mClassName As String = _
                          System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.Name

Le direttive Using (Imports) di default e la solita variabile statica per la gestione delle eccezioni.

    public FrmInputBox()
    {
      InitializeComponent();
      this.StartPosition = FormStartPosition.CenterParent;
    }
  Public Sub New()
    InitializeComponent()
    Me.StartPosition = FormStartPosition.CenterParent

  End Sub

Il costruttore, con il metodo base del designer per l'inizializzazione dei controlli, e un'istruzione che abbiamo aggiunto affinché la finestra venga visualizzata automaticamente al centro della sua finestra Parent, ovvero la finestra da cui verrà chiamata.

    public string LabelInput
    {
      get
      {
        return this.lblInput.Text;
      }
      set
      {
        this.lblInput.Text = value;
      }
    }

    public string TxtInput
    {
      get
      {
        return this.txtInput.Text;
      }
      set
      {
        this.txtInput.Text = value;
      }
    }
  Public Property LabelInput() As String
    Get
      Return Me.lblInput.Text
    End Get
    Set(ByVal value As String)
      Me.lblInput.Text = value
    End Set
  End Property

  Public Property TxtInput() As String
    Get
      Return Me.txtImput.Text
    End Get
    Set(ByVal value As String)
      Me.txtImput.Text = value
    End Set
  End Property

Due proprietà che espongono la casella di testo (per inizializzare e poi leggere il dato inserito dall'utente - notare il nome diverso tra C# e VB, dato che quest'ultimo non distingue tra maiuscole e minuscole per i nomi di membri come controlli e proprietà) e l'etichetta con il testo che descrive il dato che l'utente deve inserire.

    public static FrmInputBox BuildMe(Icon pIcon, string pTitle, string pText, string pLabel)
    {
      try
      {
        FrmInputBox frm = new FrmInputBox();
        frm.Icon = pIcon;
        frm.Text = pTitle;
        frm.TxtInput = pText;
        frm.LabelInput = pLabel;
        return (frm);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Public Shared Function BuildMe(ByVal icon As Icon, ByVal title As String, _
                                 ByVal text As String, ByVal label As String) As FrmInputBox
    Try
      Dim frm As New FrmInputBox
      frm.Icon = icon
      frm.Text = title
      frm.TxtInput = text
      frm.LabelInput = label
      Return frm

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try
  End Function

Il metodo BuildMe
Terminiamo la nostra form creando un metodo statico che, ricevuti i parametri base per la sua gestione, crea un'istanza della stessa classe form restituendola al chiamante. Questa funzione serve perché, per chiamare un'Inputbox, sarà difficilmente necessario modificare parametri diversi da icona, titolo, testo iniziale ed etichetta, quindi evitiamo del codice ripetitivo all'interno delle classi che utilizzeranno questa form.

Per testare il funzionamento della nostra InputBox, torniamo alla FrmMain ed implementiamo il codice per il metodo relativo al menu precedentemente inserito:

    private void inputBoxToolStripMenuItem_Click(object sender, EventArgs e)
    {
      FrmInputBox frm = FrmInputBox.BuildMe(this.Icon, "Test inputbox", "Modificare questo", 
                                            "Indicare il valore:");
      frm.ShowDialog();
      if (frm.DialogResult == DialogResult.OK)
      {
        Warnings.Avviso(string.Format("Il testo è {0}", frm.TxtInput));
      }
      else
      {
        Warnings.Avviso("input annullato");
      }
    }
  Private Sub InputBoxToolStripMenuItem_Click(ByVal sender As System.Object, _
                                              ByVal e As System.EventArgs) _
                                              Handles InputBoxToolStripMenuItem.Click
    Dim frm As FrmInputBox = FrmInputBox.BuildMe(Me.Icon, "Test inputbox", "Modificare questo", _
                                                 "Indicare il valore:")
    frm.ShowDialog()
    If frm.DialogResult = Windows.Forms.DialogResult.OK Then
      Warnings.Avviso(String.Format("Il testo è {0}", frm.TxtInput))
    Else
      Warnings.Avviso("input annullato")
    End If
  End Sub

Come possiamo notare, utilizziamo il metodo statico BuildMe per generare la nostra form e la mostriamo con ShowDialog. La chiamata al metodo ShowDialog non è stata inserita direttamente nel metodo BuildMe, perché in questo modo permettiamo a chi userà la form di chiamarla sia in modalità Dialog che in modalità normale, dando modo ai programmatori (tra cui noi) di lavorare più liberamente.

Lanciando l'applicazione e facendo click sul Menu InputBox, otterremo quanto mostrato in queste immagini:

la Inputbox,
il risultato alla pressione di OK
il risultato alla pressione di Cancel.

L'inputbox

Risultato alla pressione del tasto OK

Risultato della pressione del tasto Cancel

Presumiamo vi siate accorti del fatto che non abbiamo scritto alcun codice per gestire gli eventi Click dei due pulsanti in modo da ottenere il comportamento corretto. Questo è dovuto al fatto che per farli reagire correttamente è bastato indicare alla form qual è il pulsante di conferma (AcceptButton) e quale quello di annullamento (CancelButton) per fare in modo che il valore di ritorno da essi predisposto sia corretto, cioè il valore delle rispettive proprietà DialogResult.

Il fatto che ognuno dei pulsanti abbia un valore nella property DialogResult fa anche in modo che entrambi i pulsanti scatenino automaticamente la chiusura della form. Non abbiamo messo alcun tipo di controllo sull'input, né alcun blocco o formattazione, però potrebbe anche darsi che in seguito faremo delle ulteriori implementazioni su questa form, che probabilmente useremo in vari luoghi della nostra User Interface.

La form base InputComboLa classe FrmInputCombo
Dal progetto TAndT.UI, facciamo click con il tasto destro e selezioniamo Add>Windows Form> Chiamiamo la form FrmInputCombo e aggiungiamo i controlli e modifichiamo le loro proprietà come indicato qui sotto:

Disponiamo i controlli come nella figura e andiamo a scrivere il codice della form:

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace TAndT.UI
{
  public partial class FrmInputCombo : Form
  {
    private static readonly string mClassName = 
                                  System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;
Imports System
Imports System.ComponentModel
Imports System.Drawing
Imports System.Windows.Forms

Public Class FrmInputCombo

  Private Shared ReadOnly mClassName As String = _
                          System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.Name

Le direttive Using (Imports) e la variabile statica uguali a quelle della form sorella.

    public FrmInputCombo(string[] pItemsList)
    {
      InitializeComponent();
      this.cboInput.Items.AddRange(pItemsList);
      this.StartPosition = FormStartPosition.CenterParent;
    }
  Public Sub New(ByVal itemsList As String())
    InitializeComponent()
    Me.cboInput.Items.AddRange(itemsList)
    Me.StartPosition = FormStartPosition.CenterParent
  End Sub

Il costruttore, con il metodo base del designer per l'inizializzazione dei controlli, e l'istruzione che predispone la finestra per essere visualizzata automaticamente al centro della sua finestra Parent. Inoltre, questo costruttore prevede che gli sia passato obbligatoriamente un array di stringhe, che formerà la lista dei valori ammissibili.

    public string LabelInput
    {
      get
      {
        return this.lblInput.Text;
      }
      set
      {
        this.lblInput.Text = value;
      }
    }

    public string TxtInput
    {
      get
      {
        return this.cboInput.Text;
      }
      set
      {
        this.cboInput.Text = value;
      }
    }
  Public Property LabelInput() As String
    Get
      Return Me.lblInput.Text
    End Get
    Set(ByVal value As String)
      Me.lblInput.Text = value
    End Set
  End Property

  Public Property TxtInput() As String
    Get
      Return Me.cboInput.Text
    End Get
    Set(ByVal value As String)
      Me.cboInput.Text = value
    End Set
  End Property

Le property sono uguali a quelle della form InputBox, fatto salvo che il valore restituito è il Text della Combobox. Abbiamo infatti previsto una semplice Combobox contenente solo una lista di valori, non una collezione di coppie key - value come è di solito per le Combobox associate a dati.

    private void FrmInputCombo_Load(object sender, EventArgs e)
    {
      this.cboInput.SelectedItem = 0;
    }
  Private Sub FrmInputCombo_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Me.cboInput.SelectedItem = 0
  End Sub

Al caricamento della form, selezioniamo il primo valore della Combobox.

    private void FrmInputCombo_FormClosing(object sender, FormClosingEventArgs e)
    {
      if (e.CloseReason != CloseReason.ApplicationExitCall &&
        e.CloseReason != CloseReason.MdiFormClosing &&
        e.CloseReason != CloseReason.WindowsShutDown &&
        e.CloseReason != CloseReason.TaskManagerClosing)
      {
        if (DialogResult == DialogResult.OK)
        {
          if (this.cboInput.SelectedIndex == -1 &&
            this.cboInput.Text.Trim().Length > 0)
          {
            Warnings.Avviso("Il valore indicato non è valido");
            e.Cancel = true;
          }
        }
      }
    }
  Private Sub FrmInputCombo_FormClosing(ByVal sender As Object, _
                                        ByVal e As System.Windows.Forms.FormClosingEventArgs) _
                                        Handles Me.FormClosing
    If e.CloseReason <> CloseReason.ApplicationExitCall AndAlso _
       e.CloseReason <> CloseReason.MdiFormClosing AndAlso _
       e.CloseReason <> CloseReason.WindowsShutDown AndAlso _
       e.CloseReason <> CloseReason.TaskManagerClosing Then

      If DialogResult = Windows.Forms.DialogResult.OK Then
        If Me.cboInput.SelectedIndex = -1 AndAlso _
           Me.cboInput.Text.Trim.Length > 0 Then
          Warnings.Avviso("Il valore indicato non è valido")
          e.Cancel = True
        End If
      End If

    End If
  End Sub

Alla chiusura della form, verifichiamo che la chiusura non sia stata richiesta da un evento di sistema o da una forzata chiusura. Se la chiusura è richiesta da utente e se è stato premuto il tasto OK, permettiamo la chiusura della form solo se è stato selezionato un valore valido dalla Combobox.

    public static FrmInputCombo BuildMe(Icon pIcon, string pTitle, string pText, string pLabel, 
                                        string[] pItemsList)
    {
      try
      {
        FrmInputCombo frm = new FrmInputCombo(pItemsList);
        frm.Icon = pIcon;
        frm.Text = pTitle;
        frm.TxtInput = pText;
        frm.LabelInput = pLabel;
        return (frm);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Public Shared Function BuildMe(ByVal icon As Icon, ByVal title As String, ByVal text As String, _
                                 ByVal label As String, ByVal itemsList As String()) As FrmInputCombo
    Try
      Dim frm As New FrmInputCombo(itemsList)
      frm.Icon = icon
      frm.Text = title
      frm.TxtInput = text
      frm.LabelInput = label
      Return frm

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try
  End Function

Infine, anche per questa form generiamo il metodo statico BuildMe, che costruisce ed istanzia la form chiedendo solo i dati necessari all'utente, in modo da evitare codice ripetitivo nelle form dove questa form ausiliaria verrà utilizzata.

Per testare il funzionamento della nostra InputBox, torniamo alla FrmMain ed implementiamo il codice per il metodo relativo al menu precedentemente inserito:

    private void inputComboToolStripMenuItem_Click(object sender, EventArgs e)
    {
      FrmInputCombo frm = FrmInputCombo.BuildMe(this.Icon, "Test InputCombo", 
                   "Selezionare un valore", "Scegliere il valore che preferite",
                   new string[] { "Bianco", "Rosso", "Verde", "Giallo", "Blu", "Arancio", "Rosa" });
      frm.ShowDialog();
      if (frm.DialogResult == DialogResult.OK)
      {
        Warnings.Avviso(string.Format("Il valore selezionato è {0}", frm.TxtInput));
      }
      else
      {
        Warnings.Avviso("input combo annullato");
      }
    }
  Private Sub InputComboToolStripMenuItem_Click(ByVal sender As System.Object, _
                                                ByVal e As System.EventArgs) _
                                                Handles InputComboToolStripMenuItem.Click
    Dim frm As FrmInputCombo = FrmInputCombo.BuildMe(Me.Icon, "Test InputCombo", _
                       Selezionare un valore", "Scegliere il valore che preferite", _
                       New String() {"Bianco", "Rosso", "Verde", "Giallo", "Blu", "Arancio", "Rosa"})
    frm.ShowDialog()
    If frm.DialogResult = Windows.Forms.DialogResult.OK Then
      Warnings.Avviso(String.Format("Il valore selezionato è {0}", frm.TxtInput))
    Else
      Warnings.Avviso("input combo annullato")
    End If
  End Sub

Come possiamo notare, utilizziamo il metodo statico BuildMe per generare la nostra form, esattamente come per l'InputBox, però in questo caso generiamo anche l'array di stringhe contenente i valori che l'utente deve selezionare dalla combobox.
Lanciando l'applicazione e facendo click sul Menu InputBox, otterremo quanto mostrato nelle immagini:

L'inputcombo
Premuto il tasto ok con valore non valido
Risultato alla pressione del tasto ok con valore valido
Premuto il tasto cancel

Se scriviamo nel testo della combobox un valore non valido e premiamo OK, appare il messaggio di avviso e non possiamo chiudere la finestra. Se selezioniamo un valore valido, il valore viene trasmesso alla form chiamante. Se chiudiamo con il tasto Annulla otteniamo il messaggio di input annullato.

Armati di queste due nuove form, proseguiamo iniziando lo sviluppo della terza form ausiliaria.

Form specifica files per database struttura baseLa classe FrmFileSpecification
La terza form che svilupperemo, è una form accessoria che ci serve per chiedere all'utente i dati necessari a generare una FileSpecification. Per creare questo tipo di oggetto, costruiamo una form 'ad hoc' perché lo stesso oggetto viene utilizzato sia per la definizione dei file dati che per il file di log, pertanto sarebbe sciocco creare due serie di controlli identici sulla form per svolgere un unico lavoro.

Questa form è una form specializzata, non una form generica, pertanto la generiamo nel progetto UsingSqlServer. Quindi, tasto destro sul progetto UsingSqlServerAdd> Windows Form> e chiamiamo la nostra form FrmFileSpecification.

Vediamo quali controlli abbiamo allocato e come sono configurati:

Come possiamo vedere, la form non è banale nella sua forma a design, e rappresenta in forma grafica il contenuto della classe Entity FileSpecification. Facciamo notare come questa form sia diversa dalle due form di dialogo che abbiamo generato in precedenza. Questo è voluto, sia per differenziare il look delle form specializzate del nostro progetto, sia per dimostrare che vi sono molti modi per generare una interfaccia utente, in modo tale da permettervi poi di scegliere quello che considerate più consono all'uso dei vostri utenti.
Esaminiamo ora il codice:

using System;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using TAndT.Data.Entities;
using TAndT.UI;
using TAndT.UI.Forms;
Imports System
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.IO
Imports System.Text
Imports System.Windows.Forms
Imports TAndT.Data.Entities
Imports TAndT.UI
Imports TAndT.UI.Forms

Le direttive Using (Imports): oltre a quelli standard, importiamo anche tre dei namespace delle nostre librerie di base.

namespace TAndT.UsingSqlServer
{
  public partial class FrmFileSpecification : Form
  {
    private static readonly string mClassName = 
                            System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;
    FileSpecification mFileSpec;
Public Class FrmFileSpecification

  Private Shared ReadOnly mClassName As String = _
                          System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.Name
  private mFileSpec as FileSpecification

I campi privati, la solita mClassName e una FileSpecification che ci permetterà di passare i dati dalle strutture per la generazione del database a questa form e viceversa, attraverso la proprietà corrispondente:

    public FileSpecification FileSpec
    {
      get
      {
        return mFileSpec;
      }
      set
      {
        mFileSpec = value;
        LoadDataFromFileSpec();
      }
    }
  Public Property FileSpec() As FileSpecification
    Get
      Return mFileSpec
    End Get
    Set(ByVal value As FileSpecification)
      mFileSpec = value
      LoadDataFromFileSpec()
    End Set
  End Property

La proprietà che espone il campo FileSpecification e che, nella parte set, dopo la modifica del campo chiama il metodo che aggiorna il contenuto dei controlli della form:

    private void LoadDataFromFileSpec()
    {
      this.cboSize.Text = FileSpec.Size.SizeUmi;
      this.txtSize.Value = FileSpec.Size.SizeValue;
      this.cboMaxSize.Text = FileSpec.MaxSize.SizeUmi;
      this.txtMaxSize.Value = FileSpec.MaxSize.SizeValue;
      this.cboGrowth.Text = FileSpec.Growth.SizeUmi;
      this.txtGrowth.Value = FileSpec.Growth.SizeValue;
      this.txtFileName.Text = FileSpec.FileName;
      this.txtName.Text = FileSpec.Name;
    }
  Private Sub LoadDataFromFileSpec()
    Me.cboSize.Text = FileSpec.Size.SizeUmi
    Me.txtSize.Value = FileSpec.Size.SizeValue
    Me.cboMaxSize.Text = FileSpec.MaxSize.SizeUmi
    Me.txtMaxSize.Value = FileSpec.MaxSize.SizeValue
    Me.cboGrowth.Text = FileSpec.Growth.SizeUmi
    Me.txtGrowth.Value = FileSpec.Growth.SizeValue
    Me.txtFileName.Text = FileSpec.FileName
    Me.txtName.Text = FileSpec.Name
  End Sub

Il metodo di caricamento dei dati dalla proprietà FileSpec ai controlli della form, come possiamo vedere si tratta di una semplice assegnazione dei dati.

    public FrmFileSpecification()
    {
      InitializeComponent();
      this.mFileSpec = new FileSpecification(string.Empty, string.Empty);
      this.txtName.Text = string.Empty;
      this.txtFileName.Text = string.Empty;
      this.txtSize.Minimum = 0;
      this.txtSize.Maximum = int.MaxValue;
      this.txtSize.Value = 0;
      this.txtMaxSize.Minimum = 0;
      this.txtMaxSize.Maximum = int.MaxValue;
      this.txtMaxSize.Value = 0;
      this.txtGrowth.Minimum = 0;
      this.txtGrowth.Maximum = int.MaxValue;
      this.txtGrowth.Value = 0;

    }
  Public Sub New()
    InitializeComponent()
    Me.mFileSpec = New FileSpecification(String.Empty, String.Empty)
    Me.txtName.Text = String.Empty
    Me.txtFileName.Text = String.Empty
    Me.txtSize.Minimum = 0
    Me.txtSize.Maximum = Integer.MaxValue
    Me.txtSize.Value = 0
    Me.txtMaxSize.Minimum = 0
    Me.txtMaxSize.Maximum = Integer.MaxValue
    Me.txtMaxSize.Value = 0
    Me.txtGrowth.Minimum = 0
    Me.txtGrowth.Maximum = Integer.MaxValue
    Me.txtGrowth.Value = 0
  End Sub

Il costruttore della form, che inizializza i componenti e azzera i campi dei controlli.

    private void FrmFileSpecification_Load(object sender, EventArgs e)
    {
      try
      {

        this.cboSize.Items.AddRange(UsingSqlServerConfig.SizeUmiList);
        this.cboMaxSize.Items.AddRange(UsingSqlServerConfig.MaxSizeUmiList);
        this.cboGrowth.Items.AddRange(UsingSqlServerConfig.GrowthUmiList);
        if (this.txtFileName.Text.Trim().Length == 0)
        {
          this.txtFileName.Text = Path.Combine(UsingSqlServerConfig.DbFilesFolder, this.txtName.Text);
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub FrmFileSpecification_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                                     Handles Me.Load
    Try
      Me.cboSize.Items.AddRange(UsingSqlServerConfig.SizeUmiList)
      Me.cboMaxSize.Items.AddRange(UsingSqlServerConfig.MaxSizeUmiList)
      Me.cboGrowth.Items.AddRange(UsingSqlServerConfig.GrowthUmiList)
      If Me.txtFileName.Text.Trim.Length = 0 Then
        Me.txtFileName.Text = Path.Combine(UsingSqlServerConfig.DbFilesFolder, Me.txtName.Text)
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

L'event handler del caricamento della form, che provvede a caricare nelle combobox le tre liste dei valori ammessi dalle unità di misura. Con le StringCollection, si sarebbe dovuto passare attraverso la copia degli elementi in un array di String.

Se ben ricordate, abbiamo inserito nella struttura dei setting di configurazione dell'applicazione una stringa ove memorizzare ciascuna delle tre liste delle unità di misura, come potete vedere nella figura accanto, che mostra la FrmConfig.

Così possiamo osservare non solo i setting per le unità di misura della specifica di file, ma anche il nuovo setting per la cartella standard dei file di database.

Nell'event handler del caricamento della form, oltre al riempimento delle combobox, viene controllato se il percorso di destinazione è vuoto, nel qual caso viene generato un path di base per facilitare l'operatore, usando il nome logico del file e la cartella indicata nel setting DbFilesFolder.

Form di Configurazione con setting Unità di misura
    private void btnSearchPath_Click(object sender, EventArgs e)
    {
      try
      {

        ofd.Title = "Selezione destinazione file dati";
        ofd.Multiselect = false;
        ofd.FileName = string.Empty;
        ofd.Filter = "All Files (*.*)|*.*";
        ofd.FilterIndex = 0;
        if (ofd.ShowDialog() == DialogResult.OK)
        {
          this.txtFileName.Text = ofd.FileName;
          FileInfo fInfo = new FileInfo(ofd.FileName);
          this.txtName.Text = fInfo.Name.Remove(fInfo.Name.IndexOf('.'));
        }

      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub btnSearchPath_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                  Handles btnSearchPath.Click
    Try
      ofd.Title = "Selezione destinazione file dati"
      ofd.Multiselect = False
      ofd.FileName = String.Empty
      ofd.Filter = "All Files (*.*)|*.*"
      ofd.FilterIndex = 0
      If ofd.ShowDialog = DialogResult.OK Then
        Me.txtFileName.Text = ofd.FileName
        Dim fInfo As New FileInfo(ofd.FileName)
        Me.txtName.Text = fInfo.Name.Remove(fInfo.Name.IndexOf("."c))
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Il gestore dell'evento Click del pulsante di selezione di percorso. In esso si mostra come utilizzare la OpenFileDialog per selezionare la cartella di destinazione del nostro file di database: assegnamo alle proprietà i valori che ci servono per indicare al controllo come comportarsi (un titolo, il divieto di selezionare più files, il valore iniziale vuoto, un semplice "Filtro" che permette di vedere tutti i file), quindi usiamo il metodo ShowDialog che istanzia la form e ci permette di usarla per decidere dove mettere il file dati. Il nome del file viene poi copiato nella Textbox e, senza estensione, nella casella del nome logico del file.

    private void tooOk_Click(object sender, EventArgs e)
    {
      try
      {

        this.FileSpec.Name = this.txtName.Text;
        this.FileSpec.FileName = this.txtFileName.Text;
        this.FileSpec.Size.SizeValue = Convert.ToInt32(this.txtSize.Value);
        this.FileSpec.Size.SizeUmi = this.cboSize.Text;
        this.FileSpec.MaxSize.SizeValue = Convert.ToInt32(this.txtMaxSize.Value);
        this.FileSpec.MaxSize.SizeUmi = this.cboMaxSize.Text;
        this.FileSpec.Growth.SizeValue = Convert.ToInt32(this.txtGrowth.Value);
        this.FileSpec.Growth.SizeUmi = this.cboGrowth.Text;
        this.DialogResult = DialogResult.OK;

      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub TooOk_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                           Handles TooOk.Click
    Try
      Me.FileSpec.Name = Me.txtName.Text
      Me.FileSpec.FileName = Me.txtFileName.Text
      Me.FileSpec.Size.SizeValue = Convert.ToInt32(Me.txtSize.Value)
      Me.FileSpec.Size.SizeUmi = Me.cboSize.Text
      Me.FileSpec.MaxSize.SizeValue = Convert.ToInt32(Me.txtMaxSize.Value)
      Me.FileSpec.MaxSize.SizeUmi = Me.cboMaxSize.Text
      Me.FileSpec.Growth.SizeValue = Convert.ToInt32(Me.txtGrowth.Value)
      Me.FileSpec.Growth.SizeUmi = Me.cboGrowth.Text
      Me.DialogResult = DialogResult.OK

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

La gestione del tasto OK posto sulla barra. I valori dei controlli vengono copiati nelle rispettive proprietà dell'oggetto FileSpecification, e il valore DialogResult della form viene posto a OK, chiudendo così la form.

Come potete notare, i valori dei controlli vengono passati alla FileSpecification senza alcun controllo di validazione sui dati.
Non è una dimenticanza, ma una scelta precisa, perché le entità FileSpecification hanno già il necessario a validare il proprio contenuto al loro interno. Una ulteriore implementazione dei controlli di validazione la lasciamo ai nostri lettori, suggerendo di usare una struttura di transito per non rovinare i dati di quella ufficiale e di utilizzare l'IsValid per verificare la validità degli oggetti.

    private void tooCancel_Click(object sender, EventArgs e)
    {
      this.DialogResult = DialogResult.Cancel;
    }
  Private Sub TooCancel_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                       Handles TooCancel.Click
    Me.DialogResult = Windows.Forms.DialogResult.Cancel
  End Sub

La gestione del tasto Annulla, molto semplice, che si limita ad impostare la proprietà DialogResult della form come serve.

    public static FrmFileSpecification BuildMe(string pFrmName, Icon pIcon)
    {
      try
      {
        FrmFileSpecification frm = new FrmFileSpecification();
        frm.Icon = pIcon;
        return (frm);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Public Shared Function BuildMe(ByVal frmName As String, ByVal icon As Icon) _
                                                                     As FrmFileSpecification
    Try
      Dim frm As New FrmFileSpecification
      frm.Icon = icon
      Return frm

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                   + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try
  End Function

Infine, il metodo BuildMe, che genera un istanza della classe form, da usarsi per evitare di scrivere codice ripetitivo. Anche in questo caso, la form verrà poi trattata come form di dialogo. C'è una sola differenza rispetto alle versioni relative a InputBox e InputCombo: qui passiamo un nome per la form, che ci permetterà, se vogliamo, di istanziare più form diverse di questo tipo e utilizzare comunque il metodo VediSeIlFormEAperto per verificare se sono già aperte. Ma al momento ci limitiamo a lavorare in modo più semplice.

Bene, abbiamo concluso le form di contorno, ora passiamo alla form più corposa, succosa, e soprattutto complessa...

La classe FrmCreateDatabase
Questa form permetterà di effettuare le seguenti operazioni:

Form per creare database struttura di base

Dopo aver aggiunto una form di nome FrmCreateDatabase al progetto UsingSqlServer, inseriamo i seguenti controlli, impostandone le proprietà come indicato:

Lo schema da utilizzare per posizionare i controlli è quello in cui li abbiamo ordinati; inserendoli nell'ordine in cui sono elencati, la form assume in modo semplice la configurazione della figura. Le immagini per la ImageList si trovano nella cartella resources del progetto, L'ImageList è stata utilizzata per ridimensionare automaticamente le immagini, che altrimenti i controlli button non sono capaci di modificare, e per mostrare uno dei vari modi in cui utilizzare immagini all'interno di un controllo. Fatto il primo dei tre pannelli centrali, si può copiarlo e incollarlo per ottenere gli altri due.

Ed ora passiamo al codice che sta dietro alla form:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using TAndT.UI;
using TAndT.UI.Forms;
using TAndT.Data.Entities;
using System.IO;
using TAndT.Base;
using TAndT.Data;
Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.Text
Imports System.Windows.Forms
Imports TAndT.UI
Imports TAndT.UI.Forms
Imports TAndT.Data.Entities
Imports System.IO
Imports TAndT.Base
Imports TAndT.Data

La definizione dei namespace importati, come sempre per abbreviare i nomi delle classi utilizzate.

namespace TAndT.UsingSqlServer
{
  public partial class FrmCreateDatabase : Form
  {
    private static readonly string[] COLLATION_LIST = new string[] 
                                                             { COLLATION_Default, 
                                                             "Latin1_General_CI_AS", 
                                                             "SQL_Latin1_General_CP1_CI_AS" };
    private static readonly string mClassName = 
                            System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;

    private DatabaseGenerator dbGenerator;

  }
}
Public Class FrmCreateDatabase

  Private Shared ReadOnly mClassName As String = _
                          System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.Name
  Private Shared ReadOnly COLLATION_LIST As String() = New String() _
                                                                {COLLATION_Default, _
                                                                 "Latin1_General_CI_AS", _
                                                                 SQL_Latin1_General_CP1_CI_AS"}
  Private dbGenerator As DatabaseGenerator

La struttura e i campi della classe. Oltre la solita mClassName, la lista collation (costruita anche con una costante di cui nel seguito) e il DatabaseGenerator che ci fornisce la struttura dati ed i metodi necessari alla gestione della generazione dei database.

    private const string COLLATION_Default = "Default";
    private const string DBNAME_Default = "Nuovo_Db";
    public const string DEF_DataFile = "DataFile";
    public const string DEF_ExportFile = "DbInfo_{0}.xml";
    public const string DEF_LogFile = "LogFile";
    private const string DFG_PRIMARY = "PRIMARY";
    private const string FMP_FileLdb = "{0}.ldb";
    private const string FMP_FileMdb = "{0}.mdb";
    private const string FMP_LogName = "{0}_log";
    private const string IBX_LBLCollation = "Indicare la collation per il database";
    private const string IBX_LBLDbName = "Indicare il nome database";
    public const string IBX_LblNomeDelFilegroup = "Indicare il nome del filegroup";
    private const string IBX_TTLCollation = "Collation del Database";
    private const string IBX_TTLDbName = "Nome nuovo Database";
    public const string SINO_ConfermaCancellazioneFilegroup = 
                                                   "Confermi la cancellazione del filegroup?";
    public const string TXT_ExportFileTitle = 
                                          "Destinazione file esportazione specifica database";
    public const string TXT_FGrupUpdate = "Modifica Nome Filegroup";
    public const string TXT_ImportFilter = "File XML|*.xml|File TXT|*.txt|Tutti i file|*.*";
    public const string TXT_ImportTitle = "Nome file specifica database";
    public const string TXT_NewFGrup = "Nuovo Filegroup";
    public const string WAR_Annullato = "Operazione annullata.";
    public const string WAR_AnnullatoNewFGrup = 
               "Operazione annullata, non è stato specificato un File Dati per il FileGroup.";
    public const string WAR_AnnullatoNewLogFile = "Aggiunta file di log annullata.";
    public const string WAR_DbCreateSuccess = "Il database {0} è stato generato con successo";
    public const string WAR_DbExist = 
                   "Il Database {0} esiste già all'interno del server, operazione annullata.";
    public const string WAR_FGrupNotUpdatable = 
                                            "Non è possibile modificare il filegroup primary";
    public const string WAR_ImpossibileCancellare = 
         "Non è possibile cancellare l'ultimo file dati e rendere il filegroup non valido, " + 
         "cancellare l'intero filegroup oppure aggiungere un nuovo file prima di eliminare " + 
         "quello prescelto.";
    public const string WAR_InvalidExportCancelled = 
                           "La specifica database non è valida, impossibile esportarla, " + 
                           "verificare che la specifica sia valida prima di esportarla.";
  Private Const COLLATION_Default As String = "Default"
  Private Const DBNAME_Default As String = "Nuovo_Db"
  Public Const DEF_DataFile As String = "DataFile"
  Public Const DEF_ExportFile As String = "DbInfo_{0}.xml"
  Public Const DEF_LogFile As String = "LogFile"
  Private Const DFG_PRIMARY As String = "PRIMARY"
  Private Const FMP_FileLdb As String = "{0}.ldb"
  Private Const FMP_FileMdb As String = "{0}.mdb"
  Private Const FMP_LogName As String = "{0}_log"
  Private Const IBX_LBLCollation As String = "Indicare la collation per il database"
  Private Const IBX_LBLDbName As String = "Indicare il nome database"
  Public Const IBX_LblNomeDelFilegroup As String = "Indicare il nome del filegroup"
  Private Const IBX_TTLCollation As String = "Collation del Database"
  Private Const IBX_TTLDbName As String = "Nome nuovo Database"
  Public Const SINO_ConfermaCancellazioneFilegroup As String = _
                                                    "Confermi la cancellazione del filegroup?"
  Public Const TXT_ExportFileTitle As String = _
                                           "Destinazione file esportazione specifica database"
  Public Const TXT_FGrupUpdate As String = "Modifica Nome Filegroup"
  Public Const TXT_ImportFilter As String = "File XML|*.xml|File TXT|*.txt|Tutti i file|*.*"
  Public Const TXT_ImportTitle As String = "Nome file specifica database"
  Public Const TXT_NewFGrup As String = "Nuovo Filegroup"
  Public Const WAR_Annullato As String = "Operazione annullata."
  Public Const WAR_AnnullatoNewFGrup As String = _
                "Operazione annullata, non è stato specificato un File Dati per il FileGroup."
  Public Const WAR_AnnullatoNewLogFile As String = "Aggiunta file di log annullata."
  Public Const WAR_DbCreateSuccess As String = "Il database {0} è stato generato con successo"
  Public Const WAR_DbExist As String = _
                    "Il Database {0} esiste già all'interno del server, operazione annullata."
  Public Const WAR_FGrupNotUpdatable As String = _
                                             "Non è possibile modificare il filegroup primary"
  Public Const WAR_ImpossibileCancellare As String = _
       "Non è possibile cancellare l'ultimo file dati e rendere il filegroup non valido, " & _
       "cancellare l'intero filegroup oppure aggiungere un nuovo file prima di eliminare " & _
       "quello prescelto."
  Public Const WAR_InvalidExportCancelled As String = _
                            "La specifica database non è valida, impossibile esportarla, " & _
                            "verificare che la specifica sia valida prima di esportarla."

Le costanti. Sono dei dati di default, dei Format Pattern e i messaggi per le inputbox.
Vi abbiamo introdotto all'uso delle costanti già da un paio di puntate; non le usiamo per cattiveria, onde rendere meno comprensibile il codice, ma per renderlo più solido, evitando stringhe ovunque e stringhe ripetute.

    public FrmCreateDatabase()
    {
      InitializeComponent();
    }
  Public Sub New()
    InitializeComponent()
  End Sub

Il costruttore della form, limitato al solo richiamo del metodo di inizializzazione dei componenti, che bisogna scriversi in C#, che in VB sarebbe automaticamente prodotto, ma che Diego produce esplicitamente.

    private void FrmCreateDatabase_Load(object sender, EventArgs e)
    {
      try
      {

        EnableControls();

      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub EnableControls()

    Try
      Me.txtCollation.Enabled = False
      Me.txtDbName.Enabled = False
      Me.btnNewFileGroup.Enabled = Me.dbGenerator <> Nothing
      Me.btnNewDataFile.Enabled = Me.dbGenerator <> Nothing
      Me.btnNewLogFile.Enabled = Me.dbGenerator <> Nothing
      Me.btnDeleteDataFile.Enabled = Me.lstDataFiles.Items.Count > 0
      Me.btnDeleteFileGroup.Enabled = Me.lstGroup.Items.Count > 0
      Me.btnDeleteLogFile.Enabled = Me.lstLogFiles.Items.Count > 0
      Me.lstGroup.Enabled = Me.lstGroup.Items.Count > 0
      Me.lstDataFiles.Enabled = Me.lstDataFiles.Items.Count > 0
      Me.lstLogFiles.Enabled = Me.lstLogFiles.Items.Count > 0

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                    + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Sub

Al caricamento della form attiviamo/disattiviamo i controlli, richiamando il metodo seguente, perché, se non è stata ancora definita una struttura di definizione database, non devono essere attivi i controlli che permettono di modificarla, poiché provocherebbero degli errori a causa del fatto che gli oggetti che manipolano non esistono.

    private void EnableControls()
    {
      try
      {
        this.txtCollation.Enabled = false;
        this.txtDbName.Enabled = false;
        this.btnNewFileGroup.Enabled = this.dbGenerator != null;
        this.btnNewdataFile.Enabled = this.dbGenerator != null;
        this.btnNewLogFile.Enabled = this.dbGenerator != null;
        this.btnDeleteDataFile.Enabled = this.lstDataFiles.Items.Count > 0;
        this.btnDeleteFileGroup.Enabled = this.lstGroup.Items.Count > 0;
        this.btnDeleteLogFile.Enabled = this.lstLogFiles.Items.Count > 0;
        this.lstGroup.Enabled = this.lstGroup.Items.Count > 0;
        this.lstDataFiles.Enabled = this.lstDataFiles.Items.Count > 0;
        this.lstLogFiles.Enabled = this.lstLogFiles.Items.Count > 0;

      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Private Sub EnableControls()

    Try
      Me.txtCollation.Enabled = False
      Me.txtDbName.Enabled = False
      Me.btnNewFileGroup.Enabled = Me.dbGenerator <> Nothing
      Me.btnNewDataFile.Enabled = Me.dbGenerator <> Nothing
      Me.btnNewLogFile.Enabled = Me.dbGenerator <> Nothing
      Me.btnDeleteDataFile.Enabled = Me.lstDataFiles.Items.Count > 0
      Me.btnDeleteFileGroup.Enabled = Me.lstGroup.Items.Count > 0
      Me.btnDeleteLogFile.Enabled = Me.lstLogFiles.Items.Count > 0
      Me.lstGroup.Enabled = Me.lstGroup.Items.Count > 0
      Me.lstDataFiles.Enabled = Me.lstDataFiles.Items.Count > 0
      Me.lstLogFiles.Enabled = Me.lstLogFiles.Items.Count > 0

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                    + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Sub

Il metodo di attivazione/disattivazione dei controlli richiamato in vari punti del programma, per attivare i controlli della form  o disattivarli se non possono essere usati. Le textbox per il nome database e la collation sono sempre disattivate, poiché questi due dati verranno richiesti alla definizione della struttura database e non saranno modificabili in modo diretto. I bottoni e le listbox invece saranno attivati in base allo stato della struttura di definizione database in modo da impedire errori dovuti alla mancanza di dati o al fatto che la struttura non è ancora stata inizializzata.

    private void nuovaSpecificaToolStripMenuItem_Click(object sender, EventArgs e)
    {
      try
      {
        FrmInputBox frmDbName = FrmInputBox.BuildMe(this.Icon, IBX_TTLDbName, DBNAME_Default, 
                                                    IBX_LBLDbName);
        if (frmDbName.ShowDialog() == DialogResult.OK)
        {

          FrmInputCombo frmCollation = FrmInputCombo.BuildMe(this.Icon, IBX_TTLCollation, 
                                                             COLLATION_Default, IBX_LBLCollation, 
                                                             COLLATION_LIST);
          if (frmCollation.ShowDialog() == DialogResult.OK)
          {

            this.dbGenerator = new DatabaseGenerator(frmDbName.TxtInput);
            if (frmCollation.TxtInput.Trim().ToUpper() != COLLATION_Default.ToUpper())
            {
              this.dbGenerator.Collation = frmCollation.TxtInput;
            }
            this.dbGenerator.DataFiles.Add(new DataFileGroup(DFG_PRIMARY));
            this.dbGenerator.DataFiles[DFG_PRIMARY].IsPrimary = true;
            string fileName = string.Format(FMP_FileMdb, this.dbGenerator.DatabaseName);
            string fileFullName = Path.Combine(UsingSqlServerConfig.DbFilesFolder, fileName);
            this.dbGenerator.DataFiles[DFG_PRIMARY].FileSpecs.Add(this.dbGenerator.DatabaseName, 
                                                                  fileFullName);
            string logName = string.Format(FMP_LogName, this.dbGenerator.DatabaseName);
            fileName = string.Format(FMP_FileLdb, logName);
            fileFullName = Path.Combine(UsingSqlServerConfig.DbFilesFolder, fileName);
            this.dbGenerator.LogFileSpec.Add(logName, fileFullName);

            this.txtDbName.Text = this.dbGenerator.DatabaseName;
            this.txtCollation.Text = StringHelper.IsNullOrTrimEmpty(
                       this.dbGenerator.Collation) ? COLLATION_Default : this.dbGenerator.Collation;

            LoadLists();
            EnableControls();
          }
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub NuovaSpecificaToolStripMenuItem_Click(ByVal sender As Object, _
                                                    ByVal e As System.EventArgs) _
                                                    Handles NuovaSpecificaToolStripMenuItem.Click
    Try
      Dim frmBbName As FrmInputBox = FrmInputBox.BuildMe(Me.Icon, IBX_TTLCollation, DBNAME_Default, _
                                                         IBX_LBLDbName)
      If frmBbName.ShowDialog = Windows.Forms.DialogResult.OK Then
        Dim frmCollation As FrmInputCombo = FrmInputCombo.BuildMe(Me.Icon, IBX_TTLCollation, _
                                                                  COLLATION_Default, _
                                                                  IBX_LBLCollation, COLLATION_LIST)
        If frmCollation.ShowDialog = Windows.Forms.DialogResult.OK Then
          Me.dbGenerator = New DatabaseGenerator(frmBbName.TxtInput)
          If frmCollation.TxtInput.Trim.ToUpper <> COLLATION_Default.ToUpper Then
            Me.dbGenerator.Collation = frmCollation.TxtInput
          End If
          Me.dbGenerator.DataFiles.Add(New DataFileGroup(DFG_PRIMARY))
          Me.dbGenerator.DataFiles(DFG_PRIMARY).IsPrimary = True

          Dim fileName As String = String.Format(FMP_FileMdb, Me.dbGenerator.DatabaseName)
          Dim fileFullName As String = Path.Combine(UsingSqlServerConfig.DbFilesFolder, fileName)
          Me.dbGenerator.DataFiles(DFG_PRIMARY).FileSpecs.Add(Me.dbGenerator.DatabaseName, _
                                                              fileFullName)

          Dim logName As String = String.Format(FMP_LogName, Me.dbGenerator.DatabaseName)
          fileName = String.Format(FMP_FileLdb, logName)
          fileFullName = Path.Combine(UsingSqlServerConfig.DbFilesFolder, fileName)
          Me.dbGenerator.LogFileSpec.Add(logName, fileFullName)

          Me.txtDbName.Text = Me.dbGenerator.DatabaseName
          If StringHelper.IsNullOrTrimEmpty(Me.dbGenerator.Collation) Then
            Me.txtCollation.Text = COLLATION_Default
          Else
            Me.txtCollation.Text = Me.dbGenerator.Collation
          End If
          LoadLists()
          EnableControls()
        End If
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try

  End Sub

Il gestore dell'evento Click del menu per una nuova specifica database. Cominciamo ad usare le form ausiliarie che abbiamo definito. Infatti, per prima cosa ci serve dare un nome al nostro database, quindi utilizziamo una FrmInputBox. Per ora non facciamo controlli di validazione, ma in seguito potremmo farlo per rendere l'oggetto più professionale.
Se l'utente inserisce un nome per il database, passiamo a chiedergli la collation da utilizzare e sperimentiamo la FrmInputCombo. Anche qui il controllo è minimo, anche se su una combobox è relativamente facile.
Se l'utente inserisce e conferma la collation, istanziamo l'oggetto DbGenerator e definiamo immediatamente il filegroup PRIMARY per il database. Inoltre, utilizzando il setting DbFilesFolder, già definito, creiamo le specifiche di base per il file dati ed il file di log del nostro database.

Fatto questo, provvediamo a caricare i controlli della form con tutti i dati - usiamo anche il metodo LoadLists di cui nel seguito - e attiviamo i controlli che permettono di fare le modifiche (EnableControls).

Form disattivata nessuna specifica generata
La form appena aperta, disattivata.
Form Attivata dopo la generazione della specifica
La form dopo la definizione della specifica database con i controlli attivi.
    private void LoadLists()
    {
      try
      {
        LoadGroups();
        if (this.dbGenerator.DataFiles.Count > 0)
        {
          this.lstGroup.SelectedValue = DFG_PRIMARY;
        }
        LoadLogs();
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }

    }
  Private Sub LoadLists()
    Try
      LoadGroups()
      Me.lstGroup.SelectedValue = DFG_PRIMARY
      LoadLogs()

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try
  End Sub

Il metodo LoadLists carica la lista dei filegroup, attiva il filegroup PRIMARY e carica la lista dei file di log (vedi in seguito).
Invece non viene caricata la lista dei file dati collegati al filegroup selezionato, in quanto l'operazione di caricamento della listbox centrale è delegata al gestore dell'evento SelectedIndexChanged della listbox dei Filegroup, che implementeremo tra poco.

    private void LoadGroups()
    {
      this.lstGroup.Items.Clear();
      foreach (DataFileGroup dfg in this.dbGenerator.DataFiles)
      {
        this.lstGroup.Items.Add(dfg.Name);
      }
      EnableControls();
    }
  Private Sub LoadGroups()
    Me.lstGroup.Items.Clear()
    For Each dfg As DataFileGroup In Me.dbGenerator.DataFiles
      Me.lstGroup.Items.Add(dfg.Name)
    Next
    EnableControls()
  End Sub

Il metodo di caricamento della lista dei FileGroup del nostro database. Fa un ciclo sulla collection dei Filegroup e carica la listbox con i loro nomi. La chiamata ad EnableControls attiva o disattiva i controlli collegati alla presenza di dati sulla collezione dei filegroup.

    private void LoadLogs()
    {
      this.lstLogFiles.Items.Clear();
      foreach (FileSpecification fsp in this.dbGenerator.LogFileSpec)
      {
        this.lstLogFiles.Items.Add(fsp.Name);
      }
      if (this.lstLogFiles.Items.Count > 0)
      {
        this.lstLogFiles.SelectedIndex = 0;
      }
      EnableControls();
    }
  Private Sub LoadLogs()
    Me.lstLogFiles.Items.Clear()
    For Each fsp As FileSpecification In Me.dbGenerator.LogFileSpec
      Me.lstLogFiles.Items.Add(fsp.Name)
    Next
    If Me.lstLogFiles.Items.Count > 0 Then
      Me.lstLogFiles.SelectedIndex = 0
    End If
  End Sub

Come il caricamento dei filegroup, anche il caricamento della lista dei file di log definiti viene è effettuato con un ciclo sulla collezione delle specifiche di file di log della nostra struttura. Viene quindi selezionato il primo elemento della lista, se presente.

    private void lstGroup_SelectedIndexChanged(object sender, EventArgs e)
    {
      try
      {

        LoadDataFiles();
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub lstGroup_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                               Handles lstGroup.SelectedIndexChanged
    Try
      LoadDataFiles()

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

L'event handler dell' evento SelectedIndexChanged della listbox dei Filegroup, questo evento viene automaticamente scatenato quando il metodo Loadlists seleziona il filegroup Primary e, come vediamo, esegue il metodo di caricamento nella listbox delle specifiche dei file dati.

    private void LoadDataFiles()
    {
      this.lstDataFiles.Items.Clear();
      foreach (FileSpecification fsp in this.dbGenerator.DataFiles[lstGroup.Text].FileSpecs)
      {
        this.lstDataFiles.Items.Add(fsp.Name);
      }
      EnableControls();
    }
  Private Sub LoadDataFiles()
    Me.lstDataFiles.Items.Clear()
    For Each fsp As FileSpecification In Me.dbGenerator.DataFiles(lstGroup.Text).FileSpecs
      Me.lstDataFiles.Items.Add(fsp.Name)
    Next
    EnableControls()
  End Sub

L'ultimo metodo di caricamento è simile ai precedenti e riempie la lista dei file dati. Quindi richiama il metodo di abilitazione dei controlli.

I metodi di Inserimento, Cancellazione, Modifica dei Filegroup

    private void btnNewFileGroup_Click(object sender, EventArgs e)
    {
      try
      {
        FrmInputBox frmInputGroup = FrmInputBox.BuildMe(this.Icon, TXT_NewFGrup, 
                                                        string.Empty, IBX_LblNomeDelFilegroup);
        if (frmInputGroup.ShowDialog() == DialogResult.OK)
        {
          DataFileGroup dfg = new DataFileGroup(frmInputGroup.TxtInput);
          if (NewDataFile(dfg) == DialogResult.OK)
          {
            this.dbGenerator.DataFiles.Add(dfg);
            LoadGroups();
            if (lstGroup.Items.Count > 0)
            {
              lstGroup.SelectedItem = frmInputGroup.TxtInput;
            }
          }
          else
          {
            Warnings.Avviso(WAR_AnnullatoNewFGrup);
          }
        }
        else
        {
          Warnings.Avviso(WAR_Annullato);
        }

      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,  System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub btnNewFileGroup_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                            Handles btnNewFileGroup.Click
    Try
      Dim frmInputGroup As FrmInputBox = FrmInputBox.BuildMe(Me.Icon, TXT_NewFGrup, String.Empty, _
                                                             IBX_LblNomeDelFilegroup)
      If frmInputGroup.ShowDialog = Windows.Forms.DialogResult.OK Then
        Dim dfg As New DataFileGroup(frmInputGroup.TxtInput)
        If NewDataFile(dfg) = Windows.Forms.DialogResult.OK Then
          Me.dbGenerator.DataFiles.Add(dfg)
          LoadGroups()
          If lstGroup.Items.Count > 0 Then
            lstGroup.SelectedItem = frmInputGroup.TxtInput
          End If
        Else
          Warnings.Avviso(WAR_AnnullatoNewFGrup)
        End If
      Else
        Warnings.Avviso(WAR_Annullato)
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Per aggiungere un filegroup alla specifica di un database, per prima cosa dobbiamo chiederne il nome, quindi utilizziamo ancora una volta la FrmInputBox per ottenere questo dato. Poi, è necessario che in questo filegroup sia specificato almeno un file dati, quindi, automaticamente viene attivata la form per la gestione dell'inserimento/modifica di un nuovo file dati, attraverso la chiamata di un metodo esposto in seguito.

Al termine dell'inserimento del nuovo file dati, se l'utente ha confermato e inserito i dati necessari, il filegroup viene generato e i dati sulla form aggiornati, altrimenti, viene visualizzato un messaggio di avviso e il filegroup non viene generato.

    private void btnDeleteFileGroup_Click(object sender, EventArgs e)
    {
      try
      {
        if (Warnings.SiNo(SINO_ConfermaCancellazioneFilegroup) == DialogResult.Yes)
        {
          DataFileGroup dfg = this.dbGenerator.DataFiles[this.lstGroup.SelectedItem.ToString()];
          this.dbGenerator.DataFiles.Remove(dfg);
          LoadGroups();
          lstGroup.SelectedIndex = 0;
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub btnDeleteFileGroup_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                                                      Handles btnDeleteFileGroup.Click
    Try
      If Warnings.SiNo(SINO_ConfermaCancellazioneFilegroup) = Windows.Forms.DialogResult.Yes Then
        Dim dfg As DataFileGroup = Me.dbGenerator.DataFiles(Me.lstGroup.SelectedItem.ToString)
        Me.dbGenerator.DataFiles.Remove(dfg)
        LoadGroups()
        lstGroup.SelectedIndex = 0
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Per cancellare un filegroup, basta selezionarlo sulla listbox, premere il bottone di cui vediamo l'event handler e confermare la cancellazione. Il filegroup verrà rimosso e i dati aggiornati. Non abbiamo messo controlli sulla cancellazione del filegroup PRIMARY, né avvisi, ma, in una realizzazione non didattica, è buona cosa considerare i vari casi e inserire controlli per guidare l'utente a non sbagliare.

    private void lstGroup_DoubleClick(object sender, EventArgs e)
    {
      try
      {
        ListBox list = (ListBox)sender;
        string name = list.SelectedItem.ToString();
        if (name != DFG_PRIMARY)
        {
          FrmInputBox frmInputGroup = FrmInputBox.BuildMe(this.Icon, TXT_FGrupUpdate, name, 
                                                          IBX_LblNomeDelFilegroup);
          if (frmInputGroup.ShowDialog() == DialogResult.OK)
          {
            this.dbGenerator.DataFiles[name].Name = frmInputGroup.TxtInput;
            LoadGroups();
            list.SelectedItem = frmInputGroup.TxtInput;

          }
        }
        else
        {
          Warnings.Avviso(WAR_FGrupNotUpdatable);
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub lstGroup_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                        Handles lstGroup.DoubleClick
    Try
      Dim list As ListBox = DirectCast(sender, ListBox)
      Dim name As String = list.SelectedItem.ToString
      If name <> DFG_PRIMARY Then
        Dim frmInputgroup As FrmInputBox = FrmInputBox.BuildMe(Me.Icon, TXT_FGrupUpdate, name, _
                                                               IBX_LblNomeDelFilegroup)
        If frmInputgroup.ShowDialog = Windows.Forms.DialogResult.OK Then
          Me.dbGenerator.DataFiles(name).Name = frmInputgroup.TxtInput
          LoadGroups()
          list.SelectedItem = frmInputgroup.TxtInput
        End If
      Else
        Warnings.Avviso(WAR_FGrupNotUpdatable)
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

La modifica di un filegroup è attivata dal doppio click sul nome dello stesso sulla listbox. Il nome è la sola cosa modificabile in un filegroup; infatti, per modificare i file dati che lo compongono, deve essere usata la lista specifica. Abbiamo inserito un controllo che permette di modificare tutti i nomi tranne quello del filegroup PRIMARY.

I metodi di Inserimento, Cancellazione, Modifica delle specifiche del file dati

    private void btnNewdataFile_Click(object sender, EventArgs e)
    {
      try
      {
        NewDataFile(this.dbGenerator.DataFiles[this.lstGroup.SelectedItem.ToString()]);
        LoadDataFiles();
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub btnNewDataFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                                                        Handles btnNewDataFile.Click
    Try
      NewDataFile(Me.dbGenerator.DataFiles(Me.lstGroup.SelectedItem.ToString))
      LoadDataFiles()

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Il gestore dell'evento Click sul pulsante btnNewdataFile chiama il metodo NewDataFile (nel seguito) e aggiorna la lista sottostante chiamando LoadDataFiles.

    private DialogResult NewDataFile(DataFileGroup pGroup)
    {
      try
      {
        FrmFileSpecification frmData = FrmFileSpecification.BuildMe(DEF_DataFile, this.Icon);
        DialogResult dlgRs = frmData.ShowDialog();
        if (dlgRs == DialogResult.OK)
        {
          pGroup.FileSpecs.Add(frmData.FileSpec);
        }
        return (dlgRs);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Private Function NewDataFile(ByVal group As DataFileGroup) As DialogResult
    Try
      Dim frmData As FrmFileSpecification = FrmFileSpecification.BuildMe(DEF_DataFile, Me.Icon)
      Dim dlgrs As DialogResult = frmData.ShowDialog
      If dlgrs = Windows.Forms.DialogResult.OK Then
        group.FileSpecs.Add(frmData.FileSpec)
      End If
      Return dlgrs

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try
  End Function

Per inserire un nuovo file dati, instanziamo una FrmFileSpecification usando il suo metodo BuildMe, apriamo la form come dialogo e, se l'utente esce salvando i dati, aggiungiamo la FileSpecification che è stata definita nella form al filegroup corrente (che è stato passato come parametro).

    private void btnDeleteDataFile_Click(object sender, EventArgs e)
    {
      try
      {
        if (this.lstDataFiles.Items.Count > 1)
        {
          string lGroupSI = this.lstGroup.SelectedItem.ToString();
          string lDatafileSI = this.lstDataFiles.SelectedItem.ToString();
          FileSpecification fsp = this.dbGenerator.DataFiles[lGroupSI].FileSpecs[lDatafileSI];
          this.dbGenerator.DataFiles[lGroupSI].FileSpecs.Remove(fsp);
          LoadDataFiles();
          if (lstDataFiles.Items.Count > 0)
          {
            lstDataFiles.SelectedIndex = 0;
          }
        }
        else
        {
          Warnings.Avviso(WAR_ImpossibileCancellare);
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub btnDeleteDataFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                                                     Handles btnDeleteDataFile.Click
    Try
      If Me.lstDataFiles.Items.Count > 1 Then
        Dim lGroupSI As String = Me.lstGroup.SelectedItem.ToString
        Dim lDatafileSI As String = Me.lstDataFiles.SelectedItem.ToString
        Dim fsp As FileSpecification = Me.dbGenerator.DataFiles(lGroupSI).FileSpecs(lDatafileSI)
        Me.dbGenerator.DataFiles(lGroupSI).FileSpecs.Remove(fsp)
        LoadDataFiles()
        If lstDataFiles.Items.Count > 0 Then
          lstDataFiles.SelectedIndex = 0
        End If
      Else
        Warnings.Avviso(WAR_ImpossibileCancellare)
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Per cancellare una specifica di file dati, facciamo prima un controllo: infatti, se è l'ultimo file dati non permettiamo sia cancellato, ma obblighiamo l'utente a cancellare tutto il filegroup, in quanto un filegroup senza almeno un file dati non è valido. Per cancellare il file dati, lo rimuoviamo dalla collezione del suo filegroup e aggiorniamo il contenuto delle liste.

    private void lstDataFiles_DoubleClick(object sender, EventArgs e)
    {
      try
      {
        ListBox lst = (ListBox)sender;
        string fileSpecName = lst.SelectedItem.ToString();
        string fileGroupName = this.lstGroup.SelectedItem.ToString();
        FrmFileSpecification frmData = FrmFileSpecification.BuildMe(DEF_DataFile, this.Icon);
        frmData.FileSpec = this.dbGenerator.DataFiles[fileGroupName].FileSpecs[fileSpecName];
        if (frmData.ShowDialog() == DialogResult.OK)
        {
          LoadDataFiles();
          lst.SelectedItem = frmData.FileSpec.Name;
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub lstDataFiles_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                    Handles lstDataFiles.DoubleClick
    Try
      Dim lst As ListBox = DirectCast(sender, ListBox)
      Dim fileSpecName As String = lst.SelectedItem.ToString
      Dim fileGroupName As String = Me.lstGroup.SelectedItem.ToString
      Dim frmData As FrmFileSpecification = FrmFileSpecification.BuildMe(DEF_DataFile, Me.Icon)
      frmData.FileSpec = Me.dbGenerator.DataFiles(fileGroupName).FileSpecs(fileSpecName)
      If frmData.ShowDialog = Windows.Forms.DialogResult.OK Then
        LoadDataFiles()
        lst.SelectedItem = frmData.FileSpec.Name
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Il doppio click sulla lista, invece, ci permette di modificare la forma di un DataFileSpecification. Anche in questo caso, apriamo la form apposita, ma prima gli passiamo la struttura della specifica di file che vogliamo modificare, in modo che i dati vengano automaticamente aggiornati quando l'utente conferma con il tasto OK. Al termine della modifica, ricarichiamo la lista dei dati.

I metodi di Inserimento, Cancellazione, Modifica delle specifiche di file di log

    private void btnNewLogFile_Click(object sender, EventArgs e)
    {
      try
      {
        FrmFileSpecification frmLog = FrmFileSpecification.BuildMe(DEF_LogFile, this.Icon);
        if (frmLog.ShowDialog() == DialogResult.OK)
        {
          this.dbGenerator.LogFileSpec.Add(frmLog.FileSpec);
          string name = frmLog.FileSpec.Name;
          LoadLogs();
          this.lstLogFiles.SelectedItem = name;
        }
        else
        {
          Warnings.Avviso(WAR_AnnullatoNewLogFile);
        }

      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub btnNewLogFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                                                        Handles btnNewLogFile.Click
    Try
      Dim frmLog As FrmFileSpecification = FrmFileSpecification.BuildMe(DEF_LogFile, Me.Icon)
      If frmLog.ShowDialog = Windows.Forms.DialogResult.OK Then
        Me.dbGenerator.LogFileSpec.Add(frmLog.FileSpec)
        Dim name As String = frmLog.FileSpec.Name
        LoadLogs()
        Me.lstLogFiles.SelectedItem = name
      Else
        Warnings.Avviso(WAR_AnnullatoNewLogFile)
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Anche i file di Log seguono la stessa forma dei file dati. Pertanto, il codice per l'inserimento, la cancellazione e la modifica della specifica di un file di log è identico a quello scritto per le specifiche dei file di dati, variano solamente i nomi e le estensioni.

    private void btnDeleteLogFile_Click(object sender, EventArgs e)
    {
      FileSpecification fsp = this.dbGenerator.LogFileSpec[this.lstLogFiles.SelectedItem.ToString()];
      this.dbGenerator.LogFileSpec.Remove(fsp);
      LoadLogs();
      if (lstLogFiles.Items.Count > 0)
      {
        lstLogFiles.SelectedIndex = 0;
      }
    }
  Private Sub btnDeleteLogFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                                                    Handles btnDeleteLogFile.Click
    Dim fsp As FileSpecification = Me.dbGenerator.LogFileSpec(Me.lstLogFiles.SelectedItem.ToString)
    Me.dbGenerator.LogFileSpec.Remove(fsp)
    LoadLogs()
    If lstLogFiles.Items.Count > 0 Then
      lstLogFiles.SelectedIndex = 0
    End If
  End Sub

Metodo di cancellazione del file di log.

    private void lstLogFiles_DoubleClick(object sender, EventArgs e)
    {
      try
      {

        ListBox lst = (ListBox)sender;
        FrmFileSpecification frmLog = FrmFileSpecification.BuildMe(DEF_LogFile, this.Icon);

        frmLog.FileSpec = this.dbGenerator.LogFileSpec[lst.SelectedItem.ToString()];
        if (frmLog.ShowDialog() == DialogResult.OK)
        {
          string name = frmLog.FileSpec.Name;
          LoadLogs();
          this.lstLogFiles.SelectedItem = name;
        }

      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub lstLogFiles_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                    Handles lstLogFiles.DoubleClick
    Try
      Dim lst As ListBox = DirectCast(sender, ListBox)
      Dim frmLog As FrmFileSpecification = FrmFileSpecification.BuildMe(DEF_LogFile, Me.Icon)
      frmLog.FileSpec = Me.dbGenerator.LogFileSpec(lst.SelectedItem.ToString)
      If frmLog.ShowDialog = Windows.Forms.DialogResult.OK Then
        Dim name As String = frmLog.FileSpec.Name
        LoadLogs()
        Me.lstLogFiles.SelectedItem = name
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Metodo di modifica del file di log.

Metodo di visualizzazione dello script SQL

    private void visualizzaScriptSQLToolStripMenuItem_Click(object sender, EventArgs e)
    {
      try
      {

        this.txtResult.Text = this.dbGenerator.ToSqlString() + Environment.NewLine + 
                              this.txtResult.Text;

      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub VisualizzaScriptSQLToolStripMenuItem_Click(ByVal sender As System.Object, _
                                                  ByVal e As System.EventArgs) _
                                                  Handles VisualizzaScriptSQLToolStripMenuItem.Click
    Try
      Me.txtResult.Text = Me.dbGenerator.ToSqlString & Environment.NewLine & Me.txtResult.Text

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Abbiamo inserito questo metodo sia per permettervi di verificare la correttezza del codice SQL generato dalle nostre classi, sia per osservare come il codice SQL che abbiamo predisposto sulle varie classi che abbiamo costruito nelle scorse puntate si combina per ottenere la stringa di creazione database, sia, infine, perché possiate usarla per creare lo script e poi includerlo in un programma che state costruendo. Come potete vedere, utilizza semplicemente la funzione ToSqlString della classe DatabaseGenerator.  Il risultato dell'esecuzione del metodo è visibile nell'immagine accanto.

Visualizzazione SQL creazione database

Il metodo di esportazione su file XML

    private void esportaSpecificaDatabaseToolStripMenuItem_Click(object sender, EventArgs e)
    {
      try
      {
        if (this.dbGenerator.IsValid())
        {
          sfd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
          sfd.Title = TXT_ExportFileTitle;
          sfd.FileName = string.Format(DEF_ExportFile, this.dbGenerator.DatabaseName);
          if (sfd.ShowDialog() == DialogResult.OK)
          {
            this.dbGenerator.WriteXml(sfd.FileName);
          }
        }
        else
        {
          Warnings.Avviso(WAR_InvalidExportCancelled);
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }

    }
  Private Sub EsportaSpecificaDatabaseToolStripMenuItem_Click(ByVal sender As System.Object, _
                                            ByVal e As System.EventArgs) _
                                            Handles EsportaSpecificaDatabaseToolStripMenuItem.Click
    Try
      If Me.dbGenerator.IsValid Then
        sfd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal)
        sfd.Title = TXT_ExportFileTitle
        sfd.FileName = String.Format(DEF_ExportFile, Me.dbGenerator.DatabaseName)
        If sfd.ShowDialog = Windows.Forms.DialogResult.OK Then
          Me.dbGenerator.WriteXml(sfd.FileName)
        End If
      Else
        Warnings.Avviso(WAR_InvalidExportCancelled)
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Il metodo di esportazione della specifica database ci permette invece di verificare il corretto funzionamento del metodo di serializzazione della nostra classe DatabaseGenerator. In questo codice, possiamo vedere anche due cose nuove, la prima è come ottenere dal sistema il path della cartella documenti dell'utente corrente, la seconda è come utilizzare la SaveFileDialog fornita dal sistema per decidere dove scrivere il nostro file di dati.

Una volta chiamato questo metodo, il file risultante è il seguente:

<?xml version="1.0" encoding="utf-8"?>
<DatabaseGenerator xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.visual-basic.it">
  <Collation />
  <DataFiles>
    <DataFileGroup>
      <FileSpecs>
        <FileSpecification>
          <FileName>d:\sql.dir\MyDb.mdb</FileName>
          <Growth>
            <SizeUmi>MB</SizeUmi>
            <SizeValue>10</SizeValue>
          </Growth>
          <MaxSize>
            <SizeUmi>MB</SizeUmi>
            <SizeValue>1024</SizeValue>
          </MaxSize>
          <Name>MyDb</Name>
          <Size>
            <SizeUmi>MB</SizeUmi>
            <SizeValue>100</SizeValue>
          </Size>
        </FileSpecification>
      </FileSpecs>
      <IsPrimary>true</IsPrimary>
      <Name>PRIMARY</Name>
    </DataFileGroup>
  </DataFiles>
  <LogFileSpec>
    <FileSpecification>
      <FileName>d:\sql.dir\MyDb_log.ldb</FileName>
      <Growth>
        <SizeUmi>%</SizeUmi>
        <SizeValue>10</SizeValue>
      </Growth>
      <MaxSize>
        <SizeUmi>MB</SizeUmi>
        <SizeValue>512</SizeValue>
      </MaxSize>
      <Name>MyDb_log</Name>
      <Size>
        <SizeUmi>MB</SizeUmi>
        <SizeValue>5</SizeValue>
      </Size>
    </FileSpecification>
  </LogFileSpec>
  <DatabaseName>MyDb</DatabaseName>
</DatabaseGenerator>

Possiamo vedere che, grazie al fatto che le classi che compongono l'oggetto DatabaseGenerator sono state predisposte per la serializzazione automatica, non abbiamo dovuto fare praticamente nulla se non scrivere una riga di codice nella classe per ottenere una copia dell'oggetto, che può essere trasportata e riutilizzata per qualsiasi scopo. In qualsiasi nostro programma ove dovessimo generare un database, infatti, possiamo utilizzare la struttura XML invece dello script SQL e, agganciando la libreria che contiene la classe DatabaseGenerator, leggere il file dell'oggetto e generare un nuovo database.

Il metodo di importazione di una specifica salvata in XML

    private void importaSpecificaDatabaseToolStripMenuItem_Click(object sender, EventArgs e)
    {
      try
      {
        ofd.Title = TXT_ImportTitle;
        ofd.Multiselect = false;
        ofd.Filter = TXT_ImportFilter;
        ofd.FilterIndex = 1;
        ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
        if (ofd.ShowDialog() == DialogResult.OK)
        {
          this.dbGenerator = DatabaseGenerator.ReadXml(ofd.FileName, false);
        }
        else
        {
          this.dbGenerator = new DatabaseGenerator(DBNAME_Default);
        }
        LoadFromGenerator();
  
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub ImportaSpecificaDatabaseToolStripMenuItem_Click(ByVal sender As System.Object, _
                                            ByVal e As System.EventArgs) _
                                            Handles ImportaSpecificaDatabaseToolStripMenuItem.Click
    Try
      ofd.Title = TXT_ImportTitle
      ofd.Multiselect = False
      ofd.Filter = TXT_ImportFilter
      ofd.FilterIndex = 1
      ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal)
      If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then
        Me.dbGenerator = DatabaseGenerator.ReadXml(ofd.FileName, False)
      Else
        Me.dbGenerator = New DatabaseGenerator(DBNAME_Default)
      End If
      LoadFromGenerator()

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Utilizziamo ancora una volta la OpenFileDialog e la funzione per la lettura della cartella Documenti dell'utente corrente e facciamo scegliere all'utente il file contenente la specifica database. In caso la selezione sia effettuata correttamente, carichiamo la specifica salvata, altrimenti azzeriamo il contenuto della form. Vi invito a provare per gioco ad aprire file non validi, per verificare come vengano restituite le eccezioni del caso.
Per popolare la form, viene chiamato il metodo seguente:

    private void LoadFromGenerator()
    {
      this.txtCollation.Text = dbGenerator.Collation;
      this.txtDbName.Text = dbGenerator.DatabaseName;
      LoadLists();
    }
  Private Sub LoadFromGenerator()
    Me.txtCollation.Text = Me.dbGenerator.Collation
    Me.txtDbName.Text = Me.dbGenerator.DatabaseName
    LoadLists()
  End Sub

Il metodo di creazione del database

    private void creaDbToolStripMenuItem_Click(object sender, EventArgs e)
    {
      try
      {
        if (!SqlHelper.ExistDatabase(UsingSqlServerConfig.CnStringMaster, 
                                     this.dbGenerator.DatabaseName))
        {
          SqlHelper.ExecCommand(UsingSqlServerConfig.CnStringMaster, this.dbGenerator.ToSqlString(), 
                                CommandType.Text);
          Warnings.Info(string.Format(WAR_DbCreateSuccess, this.dbGenerator.DatabaseName));
        }
        else
        {
          Warnings.Avviso(string.Format(WAR_DbExist, this.dbGenerator.DatabaseName));
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub CreaDBToolStripMenuItem_Click(ByVal sender As System.Object, _
                                            ByVal e As System.EventArgs) _
                                            Handles CreaDBToolStripMenuItem.Click
    Try
      If Not SqlHelper.ExistDatabase(UsingSqlServerConfig.CnStringMaster, _
                                     Me.dbGenerator.DatabaseName) Then
        SqlHelper.ExecCommand(UsingSqlServerConfig.CnStringMaster, _
                              Me.dbGenerator.ToSqlString, CommandType.Text)
        Warnings.Info(String.Format(WAR_DbCreateSuccess, Me.dbGenerator.DatabaseName))
      Else
        Warnings.Avviso(String.Format(WAR_DbExist, Me.dbGenerator.DatabaseName))
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Il metodo per la generazione del database utilizza due nuovi metodi di cui abbiamo sentito la necessità e che abbiamo inserito nella classe SqlHelper, che spiegheremo di seguito. Il primo metodo, controlla che il database che vogliamo generare non esista già sul server; il secondo invia il comando di generazione del database al server. Il risultato dell'operazione è uno dei seguenti:

Messaggio creazione database Messaggio per database esistente
    public static void OpenMe(Icon pIcon, Form pMDI)
    {
      try
      {
        FrmCreateDatabase frm = (FrmCreateDatabase)FormHelper.VediSeIlFormEAperto(
                                                            "FrmCreateDatabase", pMDI, true, false);
        if (frm == null)
        {
          frm = new FrmCreateDatabase();
          frm.Icon = pIcon;
          frm.MdiParent = pMDI;
          frm.Show();
        }
        {
          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)
    Try
      Dim frm As FrmCreateDatabase = DirectCast(FormHelper.VediSeIlFormEAperto( _
                                          "FrmCreateDatabase", mdi, True, False), FrmCreateDatabase)
      If frm Is Nothing Then
        frm = New FrmCreateDatabase
        frm.Icon = icon
        frm.MdiParent = mdi
        frm.Show()
      End If
      frm.WindowState = FormWindowState.Normal
      frm.BringToFront()

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Il metodo OpenMe per instanziare e aprire la form, da usare dal menu della FrmMain, così:

    private void creaDatabaseToolStripMenuItem_Click(object sender, EventArgs e)
    {
      FrmCreateDatabase.OpenMe(this.Icon, this);
    }
  Private Sub CreaDatabaseToolStripMenuItem_Click(ByVal sender As System.Object, _
                                                  ByVal e As System.EventArgs) _
                                                  Handles CreaDatabaseToolStripMenuItem.Click
    FrmCreateDatabase.OpenMe(Me.Icon, Me)
  End Sub

Possiamo notare che non ci serve molta fatica per utilizzare la nostra form di creazione database.

La classe SqlHelper
Abbiamo  modificato la classe SqlHelper nel seguente modo, per poter generare un nuovo database e per lavorare con esso:

using System.Data;
Imports System.Data

Abbiamo aggiunto la direttiva using (Imports) per avere a disposizione gli oggetti del namespace Data.

    private const string SQL_ExistDb = "SELECT count(name) FROM sys.databases WHERE name = @DbName";
    private const string SQL_ParamDbName = "@DbName";
  Private Const SQL_ExistDb As String = "SELECT count(name) FROM sys.databases WHERE name = @DbName"
  Private Const SQL_ParamDbName As String = "@DbName"

Le costanti che ci servono per predisporre la stringa sql per la verifica dell'esistenza del database, e per il nome del parametro base per la richiesta.

    public static void ExecCommand(string pCnString, string pCommand, CommandType pType, SqlParameter[] pParams)
    {
      SqlConnection cn = null;
      try
      {
        cn = new SqlConnection();
        cn.ConnectionString = pCnString;
        SqlCommand cmd = new SqlCommand();
        cmd.Connection = cn;
        cmd.CommandText = pCommand;
        cmd.CommandType = pType;
        if (pParams != null)
        {
          cmd.Parameters.AddRange(pParams);
        }
        cn.Open();
        cmd.ExecuteNonQuery();
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
      finally
      {
        if (cn != null)
        {
          if (cn.State == ConnectionState.Open)
          {
            cn.Close();
          }
        }
      }
    }
		
		public static void ExecCommand(string pCnString, string pCommand, CommandType pType)
    {
      ExecCommand(pCnString, pCommand, pType, null);
    }
  Public Shared Sub ExecCommand(ByVal cnString As String, ByVal command As String, _
                                ByVal type As CommandType, ByVal params As SqlParameter())
    Dim cn As SqlConnection = Nothing
    Try
      cn = New SqlConnection
      cn.ConnectionString = cnString
      Dim cmd As New SqlCommand
      cmd.Connection = cn
      cmd.CommandText = command
      cmd.CommandType = type
      If params IsNot Nothing Then
        cmd.Parameters.AddRange(params)
      End If
      cn.Open()
      cmd.ExecuteNonQuery()

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    Finally
      If cn IsNot Nothing Then
        If cn.State = ConnectionState.Open Then
          cn.Close()
        End If
      End If

    End Try
  End Sub

  Public Shared Sub ExecCommand(ByVal cnString As String, ByVal command As String, _
                                ByVal type As CommandType)
    ExecCommand(cnString, command, type, Nothing)
  End Sub

Il metodo per eseguire uno script SQL sul server. Sarà utilizzato per tutti i comandi di DDL (data definition language) sul database. Ne abbiamo predisposto due overload: uno con i parametri Sql, l'altro senza (ad esempio, il comando CreateDatabase non ha parametri).

    public static object ExecScalar(string pCnString, string pCommand, CommandType pType, SqlParameter[] pParams)
    {
      SqlConnection cn = null;
      try
      {
        cn = new SqlConnection();
        cn.ConnectionString = pCnString;
        SqlCommand cmd = new SqlCommand();
        cmd.Connection = cn;
        cmd.CommandText = pCommand;
        cmd.CommandType = pType;
        if (pParams != null)
        {
          cmd.Parameters.AddRange(pParams);
        }
        cn.Open();
        return (cmd.ExecuteScalar());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
      finally
      {
        if (cn != null)
        {
          if (cn.State == ConnectionState.Open)
          {
            cn.Close();
          }
        }
      }
    }

    public static void ExecScalar(string pCnString, string pCommand, CommandType pType)
    {
      ExecScalar(pCnString, pCommand, pType, null);
    }
  Public Shared Function ExecScalar(ByVal cnString As String, ByVal command As String, _
                                ByVal type As CommandType, ByVal params As SqlParameter()) As Object
    Dim cn As SqlConnection = Nothing
    Try
      cn = New SqlConnection
      cn.ConnectionString = cnString
      Dim cmd As New SqlCommand
      cmd.Connection = cn
      cmd.CommandText = command
      cmd.CommandType = type
      If params IsNot Nothing Then
        cmd.Parameters.AddRange(params)
      End If
      cn.Open()
      Return cmd.ExecuteScalar

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    Finally
      If cn IsNot Nothing Then
        If cn.State = ConnectionState.Open Then
          cn.Close()
        End If
      End If
    End Try
  End Function

  Public Shared Sub ExecScalar(ByVal cnString As String, ByVal command As String, _
                              ByVal type As CommandType)
    ExecScalar(cnString, command, type, Nothing)
  End Sub

Il metodo, anch'esso con un overload, per eseguire un comando che restituisce un singolo valore. Lo utilizzeremo nel metodo che verifica l'esistenza del database:

    public static bool ExistDatabase(string pCnString, string pDbName)
    {
      try
      {
        bool retExist = false;
        SqlParameter[] para = new SqlParameter[] {
          new SqlParameter( SQL_ParamDbName, pDbName)
        };
        int count = (int)ExecScalar(pCnString, SQL_ExistDb, CommandType.Text, para);
        retExist = count > 0;
        return (retExist);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Public Shared Function ExistDatabase(ByVal cnString As String, ByVal dbName As String) As Boolean
    Try
      Dim retExist As Boolean = False
      Dim para As SqlParameter() = New SqlParameter() {New SqlParameter(SQL_ParamDbName, dbName)}
      Dim count As Integer = CType(ExecScalar(cnString, SQL_ExistDb, CommandType.Text, para), _
                                   Integer)
      retExist = count > 0
      Return retExist

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try
  End Function

Il metodo che specificamente controlla l'esistenza del database sul server.

Avviso
Sabrina vi ricorda che, per l'esecuzione di tutte e due le chiamate a SQL Server inserite nella form FrmCreateDatabase, l'utente che si connette a SQL Server, sia via Trusted Connection, che via utente SQL, deve avere diritti di amministrazione elevati sul server, in quanto l'interrogazione per l'esistenza del database viene effettuata sul database Master, come pure la Create Database. Pertanto, se l'utente non appartiene al ruolo server SysAdmin di SQL Server, difficilmente potrà eseguire le operazioni che abbiamo descritto in questo articolo, pure essendovi un altro ruolo specifico  (che a memoria Sabrina non ricorda, quindi non cita, né Diego sa) che permette alcune delle operazioni di base.

Parleremo estesamente di Login, Mappature di Utenti e gruppi Windows in SQL Server, Ruoli Server e Ruoli Database, Utenti, Permessi e Password negli articoli che dedicheremo alla Sicurezza di SQL Server.

Arrivederci alla prossima puntata
Con questo, abbiamo concluso la nostra nona puntata ed abbiamo predisposto il necessario per testare ed utilizzare tutte le classi che abbiamo generato fin qui. Nelle prossime puntate, inizieremo a costruire la struttura del database che ci accompagnerà per il resto delle puntate di questa serie, esplorando quelle che sono le basi per la costruzione di un database e quanto occorre a gestirne non solo la struttura, ma anche il contenuto, cioè inserimento, modifica, cancellazione e consultazione dei dati.

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.