domenica 24 giugno 2007

Calcolare La prima o la seconda occorrenza di un dato giorno


Giocando con le date è uscita questa funzione che calcola la prima o la seconda occorrenza di un dato giorno in un mese.

/// <summary>
/// Calcola la data di prima o seconda occorrenza di un certo giorno
/// </summary>
/// <param name="dataPartenza">Data di partenza per il calcolo</param>
/// <param name="dWeek">Giorno della settimana richiesto</param>
/// <param name="secondaOccorrenza">Seconda Occorrenza</param>
/// <returns></returns>
public static System.DateTime DataOccorrenza(DateTime dataPartenza, System.DayOfWeek dWeek, bool secondaOccorrenza)
{
DateTime resultDateTime
= dataPartenza;
int giorno = 1;
if (secondaOccorrenza)
giorno
+= 7;
resultDateTime
= new System.DateTime(dataPartenza.Year,dataPartenza.Month,giorno);
if (resultDateTime.DayOfWeek != dWeek)
{
// calcoliamo la differenza assoluta tra le due posizioni
// giorno richiesto - giorno guida (1 o 8)
// e poi aggiungiamo 7 giorni per toglierli se il valore
// supera il 7 - il valore 7 è impossibile
int x = (int) dWeek - (int) resultDateTime.DayOfWeek + 7;
if (x > 7)
x
-= 7;
resultDateTime
= resultDateTime.AddDays(x);
}
return resultDateTime;
}


Riferimenti:


Esempio C#
Esempio VB.NET

sabato 23 giugno 2007

ListView e TreeView insieme per elenco cartelle e files


Ho fatto una revisione del mio precedente esempio sull'argomento:
ListView su files e directories con corrispondenti icone con VB.NET


Come si vede dall'immagine si tratta di una pallida, lontana e indegna imitazione di Explorer.

Si parte estraendo delle periferiche di archiviazione presenti utilizzando la gerarchia System.Management

SelectQuery query = new SelectQuery("SELECT * FROM Win32_LogicalDisk");
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))

Si aggiungono i nodi appartenenti alla gerarchia di cartelle:




int AggiungiNodo(TreeNode nodoPadre)
{
string cartella = nodoPadre.FullPath;
if (! cartella.EndsWith("\\"))
cartella
+= "\\";
foreach (string s in Directory.GetDirectories(cartella))
{
TreeNode nodo
= new TreeNode();
nodo.Text
= new DirectoryInfo(s).Name;
nodo.ImageIndex
= 0;
nodo.SelectedImageIndex
= 1;
nodoPadre.Nodes.Add(nodo);
}
}


Sull'evento AfterSelect prendiamo un altro livello



private void foldersTreeView_AfterSelect(object sender, TreeViewEventArgs e)
{
if (e.Node.Nodes.Count == 0)
{
AggiungiNodo(e.Node);
}
GeneraListView(e.Node.FullPath);

}


Si recupera l'icona tramite la classe helper:



[DllImport("shell32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, int uFlags);

Riferimenti:


Esempio realizzato con VB.NET
Esempio realizzato con C#

giovedì 21 giugno 2007

DataBinding con Access e Visual Studio 2005

Ho ricevuto alcuni messaggi che mi chiedono delucidazioni sul binding di Access, con questo mio post li voglio accontentare anche se forse avrei fatto meglio a non farlo, sono del parere che certe esperienze "perverse" con Access vanno fatte di nascosto e da soli :)).

Con Visual Studio è possibile generare dei programmi che si interfacciano con access quasi senza scrivere una riga di codice.

Ovviamente questa grande facilità non ci deve portare a non curarsi di manuali ed help in linea, soprattutto non ci deve portare a pensare di poter mettersi al computer e scrivere un programma solo posizionando qualche controllo con l'ide.

Generare un progetto simile a questo è molto facile, addirittura banale se si conosce come si comporta il wizard e si riesce a scrivere poche righe di codice nel punto "giusto".

Ovviamente si tratta di un progetto che può servire solo come base di studio e approfondimento.

Veniamo al progetto e alla sua costruzione:

Si parte con il creare un nuovo progetto windows forms, a progetto aperto si aggiunge un database di Access (si può anche fare copia-incolla di access nel progetto).

Si apre il wizard con la scelta delle tabelle, io ho selezionato tutte le tabelle del database anche se poi ne ho utilizzato solo 3.

Si compila il progetto in modo tale da dare la possibilità a Visual Studio di attaccare alla toolbar i nuovi oggetti prodotti (DataSet e TableAdapter, un TableAdapter per tabella).

Si prendono dalla ToolBar gli oggetti necessari

Si impostano le proprietà appropriate, impossibile sbagliare con dei nomi così espliciti :)

Ora inizia la parte del codice da inserire, parte che notoriamente divide il grano dall'oglio.

Si inserisce il codice sull'evento Load della form (o in un altro punto comunque precedente alla visualizzazione del form che apparirebbe vuota), ciascun tableadapter comincia a svolgere la propria funzione:

Nota: Per praticità inserisco solo il codice C#, il corrispondente codice VB.NET è recuperabile dal progetto.

customersTableAdapter1.Fill(this.nwindDataSet1.Customers);
ordersTableAdapter1.Fill(
this.nwindDataSet1.Orders);
order_DetailsTableAdapter1.Fill(nwindDataSet1.Order_Details);


Si aggiungono gli eventi necessari ad intercettare lo spostamento del BindingSource collegato alle tabelle Customer e Orders



bindingCustomers.PositionChanged += new EventHandler(bindingCustomers_PositionChanged);
bindingOrders.PositionChanged
+= new EventHandler(bindingOrders_PositionChanged);

Intercettare gli eventi ha lo scopo di impostare dei filtri sui dati in modo tale da far apparire sempre solo gli ordini del cliente visualizzato e solo i dettagli degli ordini dell'ordine selezionato.


void bindingCustomers_PositionChanged(object sender, EventArgs e)
{
string customerID = string.Empty;
if (bindingCustomers.Current != null)
{
DataRowView dv
= (DataRowView) bindingCustomers.Current;
customerID
= (string)dv["CustomerID"];
}
bindingOrders.Filter
= string.Format("CustomerID = '{0}'", customerID);
bindingOrders_PositionChanged(
null, null);
}

Il codice inserito serve a recuperare il customerID dall'oggetto DataRowView indicato dalla proprietà "Current" di bindingCustomers, per forza di cose è necessario sincerarsi che tale oggetto non sia nullo.
Successivamente è possibile impostare un filtro sugli ordini.
bindingOrders_PositionChanged(null, null); invece serve per "svegliare" bindingOrders in modo tale da causare una successiva reposition con emissione dell'evento.


L'altro evento


void bindingOrders_PositionChanged(object sender, EventArgs e)
{
int orderID = 0;
if (bindingOrders.Current != null)
{
DataRowView dv
= (DataRowView)bindingOrders.Current;
orderID
= (int)dv["orderID"];
}
bindingDetail.Filter
= string.Format("OrderID = {0}", orderID);
}

E' utilizzato per impostare il filtro sul dettaglio degli ordini, sono visualizzati pertanto solo i dettagli che appartengono all'ordine.

Questo è quanto! :)

Riferimenti:

Esempio VB.NET
Esempio C#

domenica 17 giugno 2007

Cambiare il proprio numero di IP da programma

Giocando con System.Management è venuta fuori questa cosina che permette di cambiare il  proprio IP.
Si fa una query su Win32_NetworkAdapterconfiguration

ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");

E successivamente si può aggiornare con il nuovo numero di IP



foreach (ManagementObject mo in mc.GetInstances())
{
if (!(bool)mo["IPEnabled"])
continue;
inPar
= mo.GetMethodParameters("EnableStatic");
inPar[
"IPAddress"] = new string[] {NuovoIP};
inPar[
"SubnetMask"] = new string[] { NuovaNetMask };
outPar
= mo.InvokeMethod("EnableStatic", inPar,null);
break;
}

Riferimenti:

Esempio VB.NET
Esempio C#

Array Delegate e Metodi Anonimi - Esportare un DataReader in un file di testo

Per esportare un DataReader su un file di testo il framework ci mette parecchi metodi a disposizione.
L'apertura del database è quella "classica" 

C# 

SqlConnection cn = new SqlConnection(Settings.Default.Connessione);
SqlCommand cm
= new SqlCommand("SELECT * FROM customers", cn);


VB.NET



Dim cn As New SqlConnection(My.Settings.Connessione)
Dim cm As New SqlCommand(
"SELECT * FROM customers", cn)


Il successivo ExecuteReader dato sul command genera l'oggetto DataReader.


Le righe poi vengono lette tramite un ciclo sul datareader:


while (dr.Read())

All'interno del ciclo ogni oggetto "dr" è un array di object ciascuno contenente i dati della tabella correlata.
La scrittura in un file dell'array è a questo punto solamente una questione di preferenze e conoscenze.


Il metodo più classico è scorrere l'array con un ciclo:
C#



for (int k = 0; k < dr.FieldCount; k++)


VB.NET

For k As Integer = 0 To dr.FieldCount - 1


E successivamente scrivere il file di testo con i metodi Write o WriteLine dello StreamWriter.


Alternativa - Conversione del datareader in un array e scrittura dell'arrray sullo stream
Si potrebbe pensare che l'opzione "scrittura array" sia folle quando si ha già a disposizione tutta la riga del datareader già pronta per essere inviata al file.

Mi solleticava questa opzione e l'ho esplorata il trucco è partire da un array di oggetti e usare il metodo GetValues del DataReader.
Purtroppo non è possibile convertire direttamente un DataReader in un array di stringhe perchè l'unico parametro accettato è un array di object.
E' ovvio che per poter utilizzare l'opzione string.Join che concatena un array e restituisce la riga già pronta per l'invio allo stream è necessario convertire ulteriormente l'array di object in array di stringhe.

C# 



object[] objRiga = new object[dr.FieldCount];
int i = dr.GetValues(objRiga);
string[] datiRiga = new string[dr.FieldCount];


VB.NET



Dim objRiga As Object() = New Object(dr.FieldCount - 1) {}
Dim i As Integer = dr.GetValues(objRiga)
Dim datiRiga As String() = New String(dr.FieldCount - 1) {}


Nota: la gestione degli array è diversa tra C# e VB.NET infatti quando si inserisce il "quanti" su VB.NET si deve tener conto che tale numero è l'Ubound dell'array, non dice la dimensione ma l'ultimo elemento raggiungibile, partendo da 0 è ovvio che Dim c() As New Object(10) genera 11 elementi


A questo punto, nella mia ricerca sono saltate fuori due possibilità diverse



  1. Convertire l'array in stringa utilizzando Array.Copy

  2. Convertire l'array in stringa richiamando un metodo anonimo da passare come delegate alla funzione Array.ConvertAll<>(), ovviamente si tratta di utilizzare i Generics

Come si potrà vedere entrambi i metodi per la mia applicazione, hanno dei problemi 


Array.Copy richiede che il tipo di provenienza sia uguale al tipo di destinazione, in un datarow è ovvio che non ci sono solo stringhe, quindi ho commentato Array.Copy e mi sono fatto la mia funzione privata di conversione in stringa.


Ad Array.ConvertAll invece non piacciono i null


C#



// scelto il metodo di copiare l'array
string[] MetodoArray(object[] objRiga)
{
string[] datiRiga = new string[objRiga.Length];
// questo metodo non funziona perchè ci sono campi con tipi diversi
// Array.Copy(objRiga, 0,datiRiga,0,datiRiga.Length);
// ritorniamo al metodo classico
for (int i = 0; i < objRiga.Length; i++)
{
datiRiga[i]
= objRiga[i] as string;
}
return datiRiga;
}
// il più elegante, utilizzare i delegate
string[] MetodoDelegate(object[] objRiga)
{
// questo metodo non ammette null su objRiga
// sono pertanto costretto a togliere i null dalla piazza
for (int i = 0; i < objRiga.Length; i++)
{
if (objRiga[i].Equals(DBNull.Value))
objRiga[i]
= String.Empty;
}
return Array.ConvertAll<object, string>(objRiga, delegate(object obj) { return (string)obj; });
}


VB.NET


 


' scelto il metodo di copiare l'array
Private Function MetodoArray(ByVal objRiga As Object()) As String()
Dim datiRiga As String() = New String(objRiga.Length - 1) {}
' questo metodo non funziona perchè ci sono campi con tipi diversi
' Array.Copy(objRiga, 0,datiRiga,0,datiRiga.Length);
' ritorniamo al metodo classico
For i As Integer = 0 To objRiga.Length - 1
datiRiga(i)
= TryCast(objRiga(i), String)
Next
Return datiRiga
End Function
' il più elegante, utilizzare i delegate
Private Function MetodoDelegate(ByVal objRiga As Object()) As String()
For i As Integer = 0 To objRiga.Length - 1
' questo metodo non ammette null su objRiga
' sono pertanto costretto a togliere i null dalla piazza
If objRiga(i).Equals(DBNull.Value) Then
objRiga(i)
= [String].Empty
End If
Next
Return Array.ConvertAll(Of Object, String)(objRiga, AddressOf ConvertedAnonymousMethod1)
End Function
Private Function ConvertedAnonymousMethod1(ByVal obj As Object) As String
Return DirectCast(obj, String)
End Function

Come si potrà notare non sono riuscito a riprodurre il metodo anonimo su vb.net, l'ho sostituito con un vero metodo il cui indirizzo l'ho passato a ConvertAll.


Entrambi questi metodi mi hanno fatto imparare qualche cosa di nuovo, magari mi capiterà di utilizzarli in programmi "veri".


Riferimenti:


Progetto C#
Progetto VB.NET

Windows Live OneCare problemi con il monitoraggio continuo antivirus che utilizza la cpu al 100%

Approfitto biecamente del mio blog per chiedere aiuto agli amici che mi leggono.
Ho OneCare installato sul portatile che utilizzo per lavoro.
Acer 8100, 2gb ram, Xp Sp2, da ieri (16 giugno 2007) OneCare (o meglio msmpeng.exe che gli appartiene ha iniziato ad utilizzare il 100% del tempo cpu non lasciando spazio ad altre applicazioni.

Qualche rimedio? avviso che:

  1.  ho già scartato la disinstallazione
  2.  la disabilitazione dei servizi è esclusa.
  3. ho già inviato una segnalazione al supporto Microsoft

giovedì 14 giugno 2007

Gestione DataBinding Pocket PC 2003 - Sql Server 2005 Compact Ediction

Ho giocato un pò, tanto per vedere come funzionava il DataBinding di Sql Server 2005 Compact Ediction.

Per poter sviluppare per PDA teoricamente è sufficiente il Compact framework, invece nella realtà senza Visual Studio è praticamente impossibile.

Si comincia con il creare un nuovo progetto Smart Device Application

Si aggiunge al progetto un nuovo elemento, il database (.sdf)

Il wizard a questo punto apre la finestra di selezione ma ... visto che il database è vuoto (altrimenti se scegliete elemento esistente allora proseguite) io ho terminato il wizard.

Si va nella definizione del database che è visibile sul Server Explorer e si definisce la struttura dei dati.

Terminata la definizione della struttura è possibile aggiungere un nuovo DataSet al progetto

A questo punto si trascinano le tabelle  dal Server Explorer sulla definizione del file xsd.

La ricompilazione del progetto consente di far apparire nella toolbar il DataSet tipizzato e un TableAdapter per ogni DataTable mappata.

Questi oggetti si trascinano nella form del progetto.

Successivamente si possono posizionare i controlli dalla casella degli strumenti.

Su CF non esiste DataGridView nè BindingNavigator per cui è necessario inserire un menuitem, bottone per qualsiasi azione.

Il DataGrid del Compact Framework non è editabile ma può benissimo utilizzato per visualizzare i dati e per la Navigazione.

Alcuni appunti sul codice:

All'apertura della Form ho attivato un metodo che attiva la Fill del DataSet

private void MainForm_Load(object sender, EventArgs e)
{
this.datiDataSet1.tableprova.Columns["id"].AutoIncrementSeed = 1;
CaricaDati();
if (this.datiDataSet1.tableprova.Rows.Count == 0)
{
System.Data.SqlServerCe.SqlCeConnection cn
= new System.Data.SqlServerCe.SqlCeConnection(this.tableprovaTableAdapter1.Connection.ConnectionString);
System.Data.SqlServerCe.SqlCeCommand cm
= new System.Data.SqlServerCe.SqlCeCommand("INSERT INTO tableprova (Descrizione, Valore) VALUES(@P1, @P2)", cn);
System.Data.SqlServerCe.SqlCeParameter prm
= new System.Data.SqlServerCe.SqlCeParameter();
prm.DbType
= System.Data.DbType.String;
prm.SourceColumn
= "Descrizione";
prm.ParameterName
= "@p1";
cm.Parameters.Add(prm);
prm
= new System.Data.SqlServerCe.SqlCeParameter();
prm.ParameterName
= "@p2";
prm.SourceColumn
= "id";
prm.DbType
= System.Data.DbType.Double;
cm.Parameters.Add(prm);
try {
cn.Open();
for (int x = 1; x < 11; x++) {
cm.Parameters[
"@p1"].Value = "Descrizione " + x.ToString();
cm.Parameters[
"@p2"].Value = x * 1000 + x / 100;
cm.ExecuteNonQuery();
}
}
catch (Exception ex) {
}

finally {
if (cn.State == System.Data.ConnectionState.Open)
{
cn.Close();
}
}
CaricaDati();
}
}

Essendo troppo pigro per inserire dati manuali ho attivato un nuovo Command per inserire 10 righe in caso il DataSet fosse vuoto (okkio di non fare la stessa cosa in produzione!).


La verità era invece che volevo mostrare la gestione e l'inserimento dei parametri (pardon SqlCeParameter) che è del tutto uguale alla gestione di SqlParameter.


E' sempre meglio utilizzare questa forma di gestione dei Command, utilizzare i parametri vuol dire risparmiare quando si va in produzione, se lo si fa anche sugli esempi si assume un atteggiamento mentale "giusto" verso una corretta gestione dei parametri.


L'inserimento di nuove righe si attiva semplicemente aggiungendo una nuova riga vuota al BindingSource:


this.bindingSource1.AddNew();

La cancellazione di una riga:


if ((bindingSource1.Current != null))
{
this.bindingSource1.RemoveCurrent();
}
else
{
MessageBox.Show(
"Cancellazione impossibile");
}

Ovviamente per cancellare è necessario verificare prima che ci sia una riga da cancellare!

L'invio degli aggiornamenti:

 


this.bindingSource1.EndEdit();
this.tableprovaTableAdapter1.Update(this.datiDataSet1.tableprova);


Riferimenti


Esempio C#
Esempio VB.NET

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