【发布时间】:2020-08-29 20:24:32
【问题描述】:
问题
这部分是我自己最大的敌人。我有单元测试来验证我编写然后从 SQLite 数据库检索所有不同的基本数据类型的能力。在我的测试中,我验证了每种数据类型的几个不同值,包括(但不限于)<datatype>.MinValue 和 <datatype>.MaxValue。
当我将 decimal.MaxValue 写入数据库,然后尝试检索它时,我得到一个溢出异常 (thanks to rounding within the database itself)。
注意:我已经将我的实际类剥离到最基础的部分,并将它们放在一个测试方法中,这样我就可以更轻松地展示所有内容。
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 也会发生这种情况。我并不完全喜欢我的解决方案,因为我只是假设如果有溢出我需要最大/最小值。我还想概括它,这样它就不会硬编码我可能需要的最小/最大值。再次,我找到了解决方案;但同样,它很丑陋(一个传递类型以将值转换为然后对其进行切换的函数......恶心)。
【问题讨论】:
-
好的,'NUMBER' 是 SQLIte 中
Decimal的有效别名,但GetValue返回Object而标准 SQLIte 库中有GetDecimal方法,应该注意的范围。这是 EF 还是其他 ORM? -
无法使用最佳实践进行复制:测试创建并写入了 5 个随机小数值加上一个
Decimal.MaxValue,使用GetString()和GetDecimal(0将它们读回就好了。永远不要在 SQLite 中使用字符串插值或AddWithValue! SqlIte 可以在任何列中存储任何数据类型并可能破坏您的文件。使用带有Add(string, dbtype).Value = xxxx的准备好的语句和参数,您可以告诉 DBProvider 数据类型是什么,这样它就不必猜测了。 -
@ŇɏssaPøngjǣrdenlarp 这是一个自定义的 ORM 实现;所以我不一定会提前知道我在任何给定时间阅读的类型;虽然我想我可以打开我正在尝试阅读的类型。
标签: c# sqlite boundary overflowexception