domenica 10 giugno 2007

Visual Studio 2005 Gestione Databinding Firebird

Spronato dalla richiesta di un amico che mi invitava a cimentarmi con il databinding sul database Firebird spero di aver svolto degnamente il mio compitino.

Ovviamente c'è tantissimo materiale per Firebird e questo mio post deve essere considerato  come un esperimento fatto da un 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 è da un  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 (dalle mie parti dicono: Ad ogni pisada de can) l'autorizzazione per l'esecuzione del programma che accedeva alla rete.

Alla fine non ce l'ho più fatta anche 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ò.

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 generata attivando 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 lascio alla documentazione ufficiale presente nella cartella doc presente 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 id, descrizione, valore 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 geneator è:

create generator GID_TABLEPROVA;

Poi sono passato alla tabella e a generare un nuovo trigger



Il DDL completo della tabella:


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)

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 la stringa di connessione presa da  ConnectionStrings.com  sono riuscito a farla funzionare.
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!) di FbConnectionStringBuilder 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 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 poi l'esempio avevo già il file xsd non ho generato la classe helper per VB.NET, 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 sources" sulla form, si inserisce un datagridview e un bindingnavigator ed il gioco è fatto, ovviamente è necessario impostare tra le proprietà per il databinding dei controlli (textbox escluse perchè trascinandole assumo già tutte le proprietà corrette).


Ora iniziano le note dolenti



  • Definizione del DataAdapter, ovviamente parlare di un TableAdapter è escluso, ho cominciato pertanto a generare 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 utilizzato FbCommandBuilder per generare i miei comandi.2.



  • 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 le stesse istruzioni che avevo messo 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 che Firebird non ha modificato il programma di default e la relativa icona perchè altrimenti mi sarei proprio arrabbiato.

In conclusione spero di avere dato alcuni suggerimenti per l'utilizzo di Firebird nelle proprie applicazioni, considerando il fatto che Firebird, lo ripeto, l'ho conosciuto solamente ieri, figuriamoci cosa ne può trarre un esperto, buon lavoro!.


 


Riferimenti


Progetto C#
Progetto VB.NET


Firebird Home
Firebird Italia
Firebird Engine 2.0.1 
Firebird .NET Data Provider


Marathon

2 commenti:

pabloj ha detto...

Ciao, per un manager grafico cross platform prova Flamerobin (http://www.flamerobin.org/).
Per il problema di recuperare gli ID dalla versione 2.1 (attualmente alpha) è stata implementata la INSERT ... RETURNING che ti ritorna il valore della sequence

Luciano Bastianello ha detto...

Ti ringrazio della comunicazione, ne faccio tesoro e la utilizzerò alla prima occasione.