【问题标题】:SQLiteDataReader OverflowException using SQLiteDataReader on decimal.MaxValueSQLiteDataReader OverflowException 在 decimal.MaxValue 上使用 SQLiteDataReader
【发布时间】: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


【解决方案1】:

您可能无法执行此操作,但是当我遇到小数溢出错误时,我尝试将我的 sqlite 列类型从小数更改为实数,这样就消除了错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多