Firebird e Visual Studio 2005

Premessa
Dopo le esperienze con MySql e PostGresSql, ecco la volta di Firebird, su richiesta di un amico.

Ovviamente c’è tantissimo materiale per Firebird e qui mi limito ad esporre il mio esperimento da principiante.

Fino alla giornata di ieri avevo sentito solo parlare di Firebird e Interbase, tutto quello che conoscevo era la storia di Firebird a grandi linee.
Firebird è nato da un fork del progetto Open Source di Interbase della Borland che aveva posto i sorgenti sotto la licenza Mozilla 1.1.
Successivamente Borland ha ritirato Interbase e lo ha fatto ritornare un proprio prodotto a listino, da qui la nascita di Firebird.

Ho tentato inizialmente ad installare Firebird su Linux ma… riesco a farlo andare solo accedendoci localmente. Tutti i miei tentativi di farlo funzionare da Windows non hanno avuto successo, forse anche per la fastidiosissima presenza, a mo’ di “grande fratello”, di Windows Live One Care, che mi chiedeva ad ogni piè sospinto l’autorizzazione per l’esecuzione del programma che accedeva alla rete.
Alla fine ho mollato, perchè questi esperimenti sono un divertimento. Se cominciano a scocciare, visto che non si tratta di lavoro, passo a prendere delle scorciatoie e non approfondisco. Se mi servirà in futuro ovviamente lo farò.

Installazione di Firebird
Ho scaricato:

  1. Firebird 2.0.1 per Linux e per Windows
  2. Il manager gratuito Marathon (magari ne esiste qualcuno ugualmente gratuito e più funzionale, ma io non l’ho visto, se qualcuno me lo segnala magari lo provo)
  3. Firebird .NET Data Provider ho preso la release candidate 2.1

L’installazione sotto Linux l’ho fatta con:

  [root@lucy ~]# rpm -i FirebirdCS-2.0.1.12855-0.i686.rpm

L’installazione su Windows, invece, l’ho effettuata tramite l’installer Firebird-2.0.1.12855-1-Win32.exe, scegliendo la modalità di far partire manualmente il server senza attivare i servizi (ho già fin troppa roba da controllare nei servizi ci mancherebbe anche una cosa fatta per esperimento!)

Ho scoperto solo in seguito all’installazione su Windows di Marathon che l’installazione di Firebird su Windows installava anche gli strumenti client indispensabili per il suo funzionamento.
Firebird occupa circa 15 mb di spazio, fornisce anche un database di esempio “Employee.fdb” che non ho utilizzato.
Una sua sottocartella è “bin” dove ci sono gli eseguibili, alcuni li ho aperti per creare l’utente firebird che ho utilizzato per il mio esperimento.
Vi lascio alla documentazione ufficiale presente nella cartella doc situata nella cartella di installazione di Firebird.

Ho aggiornato il file aliases.conf mettendo alias con il percorso e il nome completo del nuovo database generato.
Ho aperto Marathon e da lì, dopo qualche minuto di pieno panico, ho generato il mio database e aggiunto la mia tabella con i suoi campi.

E’ importante a questo punto fare una digressione: ho generato una tabella con tre campi iddescrizionevalore in modo del tutto analogo a quanto già fatto con Postgresql e MySql, presenti entrambi nel mio sito.
Il campio ID lo volevo “autoincrementante” parimenti al serial di Posgresql o all’identity di Sql Server, dopo una breve ricerca su internet ho trovato quanto mi serviva per generare il mio campo.
Su Firebird è necessario definire un GID Generator per ogni campo su cui si intende attivare l’automatismo, poi si richiama una funzione GEN_ID() passando a questa il riferimento al generatore e il numero di incrementi che si vogliono.

Il comando DDL da dare per generare da codice il generator è:

  create generator GID_TABLEPROVA;

Poi ho creato la tabella e il nuovo trigger.

Questo è il comando DDL completo:

  create table TABLEPROVA(
       ID integer not null,
       DESCRIZIONE varchar(50),
       VALORE decimal(9, 3));
  /* Primary Key */
  alter table TABLEPROVA add constraint PK_TABLEPROVA primary key (ID);
  /* Foreign Key */
  /* Indexes */
  /* Triggers */
  create trigger NewID for TABLEPROVA active before insert position 0
  as
  begin
    if ((new.id is null) or (new.id = 0)) then
    begin
      new.id = gen_id( gid_tableprova, 1 );
    end
  end
  /* Grants */

Ho generato alcune righe tramite il normale comando sql per verificare il funzionamento del trigger.

  INSERT INTO TABLEPROVA (DESCRIZIONE, VALORE) VALUES('testo',11.11)

Lato Visual Studio 2005
Visto che “lato Firebird” tutto funzionava, sono passato a generare un nuovo progetto C# Windows Forms con Visual Studio 2005.
La prima cosa che ho fatto è stato provare direttamente i comandi di connessione al database utilizzando il driver nativo che ho indicato tra i riferimenti del mio progetto.

Purtroppo non sono riuscito a far funzionare.la stringa di connessione presa da ConnectionStrings.com.
Sullo stesso sito ho trovato però il link al progetto di sviluppo del driver Firebird per .NET, dove, tra gli esempi, ho trovato questo frammento riguardo all’utilizzo (che sciccheria!) diFbConnectionStringBuilder che ovviamente è lo stringbuilder per le connessioni di Firebird.

      FbConnectionStringBuilder cs = new FbConnectionStringBuilder();
      cs.DataSource = "localhost";
      cs.Database = "employee.fdb";
      cs.UserID = "SYSDBA";
      cs.Password = "masterkey";
      cs.Charset = "NONE";
      cs.Pooling = false;
      FbConnection connection = new FbConnection(cs.ToString());

Fatto questo, ho visto che il mio semplice comando “SELECT …” funzionava perfettamente. Così sono passato a cose più serie.

Generare il dataset tipizzato.
Su Firebird, come per tanti altri database, non esiste un tool di definizione automatica e disegno del dataset tipizzato.
Tra le faq ho trovato un frammento per la generazione di un dataset tipizzato che però ha funzionato solo dopo alcune correzioni.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Data;
using FirebirdSql.Data;
using FirebirdSql.Data.FirebirdClient;
using System.IO;
using System.CodeDom;
using Microsoft.CSharp;

namespace BindingFirebirdCs
{
  class DataSetHelper
  {
    private const string DataSetName = "DbProva";
    private const string NomeTabella = "TableProva";
    private const string NomeFile = "TableProva.cs";
    private const string NomeSpazio = "BindingFireBird";
    private const string NomeDataSet = "DbProva.xsd";

    void GeneraDataSet()
    {
      FbConnection connection = new FbConnection(Globali.Connessione);
      DataSet dbprova = new DataSet(DataSetName);
      FbCommand command = new FbCommand("select * from tableprova", connection);
      FbDataAdapter adapter = new FbDataAdapter(command);
      adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;

      adapter.Fill(dbprova, NomeTabella);

      // Generate the employee TypedDataSet
      string fileName = NomeFile;

      StreamWriter tw = new StreamWriter(new FileStream(fileName,
          FileMode.Create,
          FileAccess.Write));

      CodeNamespace cnSpace = new CodeNamespace(NomeSpazio);
      CSharpCodeProvider csProvider = new CSharpCodeProvider();

      System.Data.Design.TypedDataSetGenerator.Generate(dbprova, cnSpace, csProvider);
      csProvider.GenerateCodeFromNamespace(cnSpace, tw, null);

      tw.Flush();
      tw.Close();

      dbprova.WriteXmlSchema(NomeDataSet);

      connection.Close();
    }

  }
}

Visto che per riprodurre l’esempio avevo già il file xsd, non ho generato la classe helper per VB.NET, ma sono a disposizione per eventuali traduzioni al costo dieci euro per istruzione :) ).

Una volta generato il dataset, è necessario associarlo al progetto mediante aggiungi elemento esistente, la successiva compilazione del progetto è necessaria per far comparire il nuovo controllo (il dataset tipizzato) tra i controlli a disposizione.

Dalla generazione del file xsd alla gestione dei campi a video è tutta discesa!
Si trascinano e posizionano i campi del “data source” sulla form, si inserisce un datagridview e un bindingnavigator ed il gioco è fatto.
Ovviamente è necessario impostare alcune proprietà per il databinding dei controlli (textbox escluse perchè trascinandole assumono già tutte le proprietà corrette).

Ora iniziano le note dolenti

  • Definizione del DataAdapter
    ovviamente parlare di un TableAdapter era escluso.
    Ho cominciato pertanto a implementare manualmente i comandi del FbDataAdapter.

          cm = new FbCommand(
            "UPDATE TABLEPROVA SET DESCRIZIONE = @descrizione, VALORE = @valore WHERE ID = @id", cn);
          cm.Parameters.Add(new FbParameter("@descrizione", FbDbType.VarChar, 50, "descrizione"));
          cm.Parameters.Add(new FbParameter("@valore", FbDbType.Decimal, 0, "valore"));
          cm.Parameters.Add(new FbParameter("@id", FbDbType.Integer, 0, ParameterDirection.Input, false, 
                            (byte)0, (byte)0, "ID", DataRowVersion.Original, DBNull.Value));
          daTableProva.UpdateCommand = cm;
          cm = new FbCommand("DELETE FROM tableprova WHERE id = @id", cn);
          cm.Parameters.Add(new FbParameter("@id", FbDbType.Integer, 0, ParameterDirection.Input, false, 
                            (byte)0, (byte)0, "ID", DataRowVersion.Original, DBNull.Value));
          daTableProva.DeleteCommand = cm;
          cm = new FbCommand("INSERT INTO \"TABLEPROVA\" (\"DESCRIZIONE\", \"VALORE\") VALUES (@p1, @p2 ", cn);
          cm.Parameters.Add(new FbParameter("@p1", FbDbType.VarChar, 50, ParameterDirection.Input, true, 
                            (byte)0, (byte)0, "DESCRIZIONE", DataRowVersion.Proposed, DBNull.Value));
          cm.Parameters.Add(new FbParameter("@p2", FbDbType.Decimal, (byte)0, ParameterDirection.Input, true, 
                            (byte)9, (byte)3, "VALORE", DataRowVersion.Proposed, DBNull.Value));
          daTableProva.InsertCommand = cm;

    Poi sono passato a provare i vari comandi, funzionavano tutti con esclusione dell’InsertCommand: non c’è stato verso di farlo funzionare! Alla fine ho desistito e ho utilizzatoFbCommandBuilder per generare i miei comandi.

  • Recupero ID
    la mia intenzione era di recuperare il nuovo ID nell’evento RowUpdated.
    Su C# ci sono riuscito (ho fatto poi un’altra scelta per la gestione dell’incremento manuale su RowUpdating).
    void daTableProva_RowUpdating(object sender, FbRowUpdatingEventArgs e)
    {
      if (e.StatementType == StatementType.Insert)
      {
        FbConnection cn = daTableProva.SelectCommand.Connection;
        FbCommand cm = new FbCommand("SELECT GEN_ID(gid_tableprova, 1) AS id FROM RDB$DATABASE", cn);
        Int64 id = (Int64)cm.ExecuteScalar();
        e.Row.AcceptChanges();
      }
    }

In parole povere: intercettando l’evento RowUpdating che si verifica quando avviene l’update prima dell’invio dei dati al database (il dataadapter scorre le righe della collection da aggiornare e per ciascuno di queste scatta l’evento).
Non so dove, ho letto un post che diceva che era necessario inserire l’istruzione e.Row.AcceptChanges() prima dell’aggiornamento perchè Firebird non lo impostava automaticamente, non so se sia vero, ma, comunque, non avevo tempo di controllare.

Con VB.NET invece la stessa istruzione per attribuire l’ultimo progressivo nella textbox del campo ID non funzionava (continuo a non sapere perchè).
Ho provveduto con un “barbatrucco”, impostando cioè il valore di default del DataColumn con il valore desiderato (è una porcheria ma funziona!).

  Private Sub bindingNavigatorAddNewItem_Click(ByVal sender As System.Object, 
                                               ByVal e As System.EventArgs) _
                                               Handles bindingNavigatorAddNewItem.Click
    Dim cn As FbConnection = daTableProva.SelectCommand.Connection
    Dim cm As New FbCommand("SELECT GEN_ID(gid_tableprova, 1) AS id FROM RDB$DATABASE", cn)
    Try
      If cn.State = ConnectionState.Closed Then
        cn.Open()
      End If
      MaxID = CType(cm.ExecuteScalar(), Integer)
      Dim dc As DataColumn = dsDati.TableProva.Columns("ID")
      dc.DefaultValue = MaxID
      'IDTextBox.Text = MaxID.ToString
    Catch ex As Exception
      MessageBox.Show(ex.Message & Environment.NewLine & ex.StackTrace)
    Finally
      If cn.State = ConnectionState.Open Then
        cn.Close()
      End If

    End Try

  End Sub

Come si può intuire dal codice presentato, ho scelto di incrementare subito il contatore ID da codice, recuperarne il valore ed assegnarlo come valore di default al DataColumn (dc.DefaultValue) in modo tale da attribuirlo alla “nascente” nuova riga

  • Nome del database
    purtroppo l’esensione di Firebird è fdb, la stessa estensione dei file che uso per lavoro e cioè il database nativo di Microsoft Dynamics NAV (ex Navision).
    Per fortuna Firebird non ha modificato il programma di default e la relativa icona perchè altrimenti mi sarei proprio arrabbiato.
Both comments and pings are currently closed.

Comments are closed.