Tak tu mám další kontroverzní příspěvek. Kolega mi nedávno hlásil nepříjemný, datově závislý problém s konverzí hodnot typu SqlDecimal na typ Decimal.
V čem, že spočívá problém ?
Typ System.Decimal má max. přesnost 29 míst, zatímco typ System.Data.SqlTypes.SqlDecimal 38 míst.
Bohužel, zřejmě i takovéto hodnoty se nám občas nacházejí v databázi a bezpečně je ošetřit není úplně "easy", na property sqlDecimal.Value zapomeňte.
.NET Framework se v tomto případě při konverzi chová přísně logicky, vyvolá vyjímku typu Conversion overflow, tzn. odmítne hodnotu zaokrouhlit, zkrátit či jinak "zmrzačit".
Na druhou stranu je velmi zajímavé, že konstruktor typu sql decimal vám z reálného čísla 10e-20 vyrobí bez problémů sql decimal s hodnotou 0 a přesností 17 míst. Čemuž teda přesně nerozumím :-(
Kolegovi jsem poradil, aby rozhodně zvážil, jak přesná čísla vlastně chce do databáze ukládat a zda je po načtení z databáze za těchto podmínek vůbec chceme někam konvertovat. No, stejně radši bacha na property sqlDecimal.Value pro konverzi hodnoty na hodnotu typu decimal.
Pokud si myslíte, že .Net framework obelstíte např. použitím metody Convert.ToDecimal, pak vás taky zklamu, ta pro sql typy nefunguje vůbec.
Našel jsem jen jediný způsob: převzorkovat hodnotu na hodnotu s nižší přesností.
A nyní příklad:
using System.Data.SqlTypes;
//k naplneni sql decimalu pouzijeme napr. hodnotu PI
na 35 desetinnych mist (kterou kazdy znate jeste ze skoly ;-))
SqlDecimal dec1 =
SqlDecimal.Parse("3.14159265358979323846264338327950288");
decimal dec2 = dec1.Value; // --> Conversion Overflow exception
decimal dec3 = SqlDecimalValue(dec1); //funguje a cislo "prevzorkuje"
na nizsi presnost
/// <summary>
/// Safe converter from SqlDecimal to Decimal value (null -> 0).
/// </summary>
/// <param name="sqlDecimal">SqlDecimal value (precision 38)</param>
/// <returns>Decimal value (precision 29)</returns>
public static decimal SqlDecimalValue(SqlDecimal sqlDecimal)
{
//osetreni null hodnoty, 'sqlDecimal.Value'
standardne vraci System.Data.SqlTypes.SqlNullValueException
if (sqlDecimal.IsNull) return 0M;
//osetreni preteceni presnosti decimalu, 'sqlDecimal.Value'
standardne vraci System.OverflowException
const int precDecimal = 29; //presnost decimalu,
lze ho urcit napr. takto: new SqlDecimal(Decimal.MaxValue).Precision;
if (sqlDecimal.Precision <= precDecimal) return sqlDecimal.Value;
//delsi cislo typu SqlDecimal je nutne prevzorkovat na nizsi presnost
typu Decimal
int scaleDecimal =
precDecimal - (sqlDecimal.Precision - sqlDecimal.Scale);
SqlDecimal convertedDecimal =
SqlDecimal.ConvertToPrecScale(sqlDecimal,precDecimal,scaleDecimal);
return convertedDecimal.Value;
}
Lze předpokládat, že obdobný problém může nastat také pro příliš vysoká čísla, ovšem pro takový případ lék nemám, změnu hodnoty na 30-tém místě zamaskuju, ale zkrácením čísla o několik řádů se uživateli rozhodně nezavděčím ;-)