L’esigenza del cliente consiste in un’aplicazione che, utilizzando SQL CE, permetta la memorizzazione di una serie di informazioni; una di queste è un valore numerico che potra variare da 0 a 99,9 con precisione massima ad un numero dopo la virgola.
Perfetto, creo un DB SQL CE contente la tabella “test” con, fra gli altri, il campo “colonna” di tipo numeric (3,1).
1: create table test
2: (
3: Id int primary key identity(1,1),
4: ...
5: colonna numeric (3,1)
6: )
Scrivo anche un test che eseguendo il codice:
1: using (var connection = new SqlCeConnection("Data Source=… "))
2: {
3: connection.Open();
4: SqlCeCommand command = connection.CreateCommand();
5: command.CommandText = "INSERT INTO Test (Colonna) VALUES (@p0)";
6: command.Parameters.Add(new SqlCeParameter("p0", 100));
7: command.ExecuteNonQuery();
8: }
si aspetta un’eccezione.
Perfetto, eseguendo il test l’eccezione viene sollevata ed il test passa.
In realtà l’applicazione non esegue direttamente le istruzioni SQL, ma scrive e legge le informazioni dal DB utilizzando una entity mappata con NHibernate, tale entity espone la property “colonna” di tipo double.
Ovviamente anche questa parte di codice è coperta da alcuni test che ne verifichino il corretto funzionamento.
In particolare scriviamo anche un test che istanziando la entity e valorizzando la property colonna con il valore 100 si aspetta un’eccezione al momento della Insert nel DB.
Ricreo la tabella ed eseguo il test.
Qui esiste nasce il problema: l’eccezione NON viene sollevata!
Ma come? Il campo non era un numeric (3,1)?
A casa mia 100 è maggiore di 99,9!
Come prima azione, dopo aver eseguito il test che inserisce il valore 100 senza aver sollevato l’eccezione, mi collego al db Sql CE con la management studio ed eseguo l’istruzione
1:
2: Select count(*) from test
Risultato = “1”.
Ma allora il record è entrato!
Eseguo l’istruzione
Risultato = “1”.
Allora eseguo l’istruzione
1: Select colonna from test
Risultato = “Major Error 0x80004005, Minor Error 0 Arithmetic Overflow”.
Dopo una decina di secondi conditi da una sequenza di “brutti porchi” eseguo l’istruzione
1: Select cast([COLONNA] as numeric(4,1)) from test
Risultato = “100”.
Colto da un dubbio amletico vado a controllare le proprietà del campo colonna e vedo: “numeric (3,1)”
Premesso che ci sono mille soluzioni per aggirare il problema vorrei capire perchè esiste questo comportamento!
Alcune premesse:
La versione del mapping di NHibernate utilizzata è la seguente
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" >
la property è semplicemente mappata così:
<property name="Colonna"/>
Il dialetto è quello per Sql CE:
<property name="dialect">NHibernate.Dialect.MsSqlCeDialect</property>
Ora vorrei rispondere alla domanda: “Perchè inserendo il valore 100 con un’istruzione sql ottengo (giustamente) l’eccezione mentre con NHibernate no?”
Scaricando il codice di NHibernate (http://sourceforge.net/projects/nhibernate/ ) e modificando la reference del mio progetto agganciandomi così al codice posso facilmente debuggare quello che avviene al “Save” del mio repository.
Dietro le quinte Nhibernate non fa “magie”, alla fine dei suoi ragionamenti genera una SqlCeConnection ed un SqlCeCommand e ne esegue il metodo ExecuteNonQuery.
L’unica particolarità consiste nel SqlDbType utilizzato da Nhibernate, se la property della mia entity è definita come double Nhibernate forza il SqlDbType del parametro a “Float” e non “Decimal”.
Perfetto: abbiamo risposto alla domanda, ci basta mappare la property così:
<property name="Colonna" type="Decimal" />
ed NHibernate utilizzando il SqlDbType corretto genererà la giusta eccezione.
Ora però mi sorge un’altra domanda: io posso anche accettare che mi si imponga quale SqlDbType utilizzare ma se il campo è numeric (3,1) perchè SQL CE si è memorizzato il valore 100?
A questa domanda non ho ancora saputo rispondere... idee?
Riccardo.