問題
これは部分的に私が自分自身の最悪の敵であることです. SQLite データベースとの間ですべての異なる基本データ型を書き込んで取得する能力を検証する単体テストがあります。私のテストの中で、(これらに限定されません)<datatype>.MinValue
とを含む各データ型のいくつかの異なる値を検証し<datatype>.MaxValue
ます。
データベースにa を書き込んでdecimal.MaxValue
取得しようとすると、オーバーフロー例外が発生します (データベース自体の丸めのおかげです)。
注:実際のクラスを必要最低限の要素まで取り除き、それらをテスト メソッド内に配置して、すべてをより簡単に表示できるようにしました。
private static SQLiteConnection connection;
[TestMethod()]
public void WriteDecimal()
{
using (var cmd = new SQLiteCommand(connection))
{
cmd.CommandText = $"INSERT INTO foo(name, value) VALUES('bar', {decimal.MaxValue})";
cmd.ExecuteNonQuery();
cmd.CommandText = "SELECT * FROM foo;";
using (SQLiteDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
Console.WriteLine($"{rdr.GetInt32(0)} {rdr.GetString(1)} {rdr.GetValue(2)}");
}
}
}
}
#region Setup/Cleanup
[ClassInitialize()]
public static void Setup(TestContext context)
{
FileInfo dbFile = new FileInfo(Path.Combine(Environment.GetEnvironmentVariable("temp"), @"\Sqlite\myDb.db"));
dbFile.Directory.Create();
dbFile.Delete();
string connectionString = $"Data Source={dbFile?.FullName ?? ":memory:"}";
connection = new SQLiteConnection(connectionString);
connection.Open();
using (var cmd = new SQLiteCommand(connection))
{
cmd.CommandText = @"CREATE TABLE foo(id INTEGER PRIMARY KEY, name TEXT, value Number)";
cmd.ExecuteNonQuery();
};
}
[ClassCleanup()]
public static void Cleanup()
{
connection.Close();
}
#endregion
出力:
Message:
Test method WriteDecimal threw exception:
System.OverflowException: Value was either too large or too small for a Decimal.
Stack Trace:
Number.ThrowOverflowException(TypeCode type)
DecCalc.VarDecFromR8(Double input, DecCalc& result)
IConvertible.ToDecimal(IFormatProvider provider)
Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
SQLite3.GetValue(SQLiteStatement stmt, SQLiteConnectionFlags flags, Int32 index, SQLiteType typ)
SQLiteDataReader.GetValue(Int32 i)
DatabaseDirect.WriteDecimal() line 54
回避策
回避策を見つけました(気に入らないだけです)。基本的に、失敗させてから、戻って Double として取得しようとします。次に、必要なものに変換します。オーバーフローしたため、最大値または最小値のいずれかでなければならないことがわかっています。
using (SQLiteDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
decimal newVal;
try
{
newVal = (decimal)rdr.GetValue(2);
}
catch (OverflowException)
{
double val = rdr.GetDouble(2);
Type t = rdr.GetFieldType(2);
newVal = val > 0 ? decimal.MaxValue : decimal.MinValue;
}
Console.WriteLine($"{rdr.GetInt32(0)} {rdr.GetString(1)} {newVal}");
}
}
より大きな問題(私が見ているように)
この問題に遭遇したのはこれだけではありません。また、decimal.MinValue および ulong.MaxValue でも発生します。オーバーフローが発生した場合に最大/最小値が必要であると想定しているため、私は自分のソリューションのファンではありません。また、必要な最小値/最大値をハードコードしないように一般化したいと思います。繰り返しますが、解決策を見つけました。しかし、繰り返しますが、それは醜いです(型を渡して値を変換し、それを切り替える関数...厄介です)。