Decimal 类型从 In-memory Sqlite 数据库中读取为 double 或 int

Decimal type is read as double or int from In-memory Sqlite database

我对 .Net 中的 Sqlite 没有太多经验,但我看到的行为很奇怪。假设我们有一个具有以下 project.json:

的 .Net 核心应用程序
{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true
  },
  "dependencies": {
    "Microsoft.Data.Sqlite": "1.0.0",
    "Dapper": "1.50.2"
  },
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.0"
        }
      },
      "imports": "dnxcore50"
    }
  }
}

我们还有一个简单的 class Item:

public class Item
{
    public Item() { }

    public Item(int id, string name, decimal price)
    {
        this.Id = id;
        this.Name = name;
        this.Price = price;
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

然后我创建一个内存数据库并用数据填充它(使用 Dapper):

var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
connection.Execute("CREATE TABLE IF NOT EXISTS Items(Id INT, Name NVARCHAR(50), Price DECIMAL)");

var items = new List<Item>
{
    new Item(1, "Apple", 3m),
    new Item(2, "Banana", 1.4m)
};
connection.Execute("INSERT INTO Items(Id, Name, Price) VALUES (@Id, @Name, @Price)", items);

然后我尝试从 Items table:

读取
var dbItems = connection.Query<Item>("SELECT Id, Name, Price FROM Items").ToList();

当我运行解决方案时,出现以下异常:

Unhandled Exception: System.InvalidOperationException: Error parsing column 2 (Price=1.4 - Double) ---> System.Invali dCastException: Unable to cast object of type 'System.Double' to type 'System.Int64'.

好的,那我尝试用Microsoft.Data.Sqlite获取数据:

var command = connection.CreateCommand();
command.CommandText = "SELECT Price FROM Items";
var reader = command.ExecuteReader();
while (reader.Read())
{
    Console.WriteLine(reader[0].GetType());
}

结果我得到:

System.Int64 // Price = 3                                                                                                         
System.Double // Price = 1.4

我尝试 运行ning 在真实数据库中使用十进制价格进行查询,返回的数据类型是正确的并且始终是十进制(如预期的那样)。
应该往哪个方向深挖?我的内存数据库有问题吗?怎么和小数保持一致?

这是SQLite的一个特性,请看这个link:https://sqlite.org/faq.html#q3

在 SQLite 中,如果您使用 Numeric/Decimal 列数据类型创建 Table,它会关联到 数值类型亲和力 及其默认行为是如果值没有小数则存储整数,或者如果值包含小数则存储实数。这有助于节省字节存储,因为最常用的整数值(对于中小型应用程序小于一百万的值)需要比实际数据类型更少的字节:

整数(约):

  • ± 128 > 1 字节 (2^8-1)
  • ± 32,768 > 2 字节 (2^16-1)
  • ± 8,388,608 > 3 字节 (2^24-1)
  • ± 2,147,483,648 > 4 字节 (2^32-1)
  • ± 140,737,488,355,328 > 6 字节 (2^48-1)
  • ± 9,223,372,036,854,780,000 > 8 字节 (2^64-1)

针对 8 字节真实 IEEE 数据类型

如果您需要始终存储实数,则有必要声明一个 float/real 数据类型以便被视为实数类型关联,因此即使您将整数发送到 SQL它会被存储为真实的

来源:https://sqlite.org/datatype3.html

我知道 real 可能不精确,但我已经使用 C# Decimal 数据类型和 SQLite 以 wal 模式在磁盘中使用数据库进行了一些测试,如果所有操作我都没有舍入错误是用 C# 制作的。但是当我直接在 SQLite 中进行一些计算时,由于 2 个整数除法

,我得到了一些舍入错误和一些错误的计算

我使用 EF6 和 Dapper 来 read/write 数值(integer/real 来自 SQLite)并且从未出现错误。

现在,我打算利用 SQLite 数字行为来使用 dapper TypeHandler 节省磁盘 space,因此我可以复制 SQL 服务器 Money实现(在内部,SQL 服务器保存一个 8 字节的整数,并在加载到内存时除以 10000):

public class NumericTypeHandler : SqlMapper.TypeHandler<decimal>
{
    public override void SetValue(IDbDataParameter parameter, decimal value)
    {
        parameter.Value = (long)(value / 10000);
    }

    public override decimal Parse(object value)
    {
        return ((decimal)value)/10000M;
    }
}

此外,我设置了 datetime UnixEpoch 实现以节省磁盘space 并提高性能

注:

SQLite 可以在低需求 asp.net 应用程序中处理数十个用户,诀窍是使用 pragmas 指令和其他东西调整 SQLite:

  • WAL 模式:有助于管理更好的并发性和性能
  • 页面大小:如果匹配文件系统的簇大小,提高性能
  • 共享缓存模式:实现 table 锁而不是数据库锁,当它在多个线程中加入时
  • Datetime UnixEpoch:节省秒数(4 字节)而不是文本 ISO 日期时间(“2012-12-21T17:30:24”= 20 字节)。
  • 缓存大小:已保存 I/O 访问将最后访问的页面保留在内存中

我还在 C# 方面做了一些改进:

  • 我为日期时间值制作了一个自定义日期时间结构,以便将数据保存为整数覆盖 ToString 方法,并使用构造函数重载作为日期时间加载到内存中。
  • 我在 C# 中创建了一个自定义数字结构(如 DateTime 结构),以通过覆盖 ToString 方法以美分形式节省资金并使用构造函数重载以 Decimal 形式加载到内存中

避免保存不必要的数据(错误日志、电子邮件 html 文本)

如果您打算使用 SQLite 作为后端,我认为您应该使用像 EFx 或 Dapper 这样的 ORM。使用 dapper,您需要创建自己的 微框架 以节省开发时间(基本的 CRUD 功能和 Linq 查询转换)。

我找到的最好的工具是 https://github.com/linq2db/linq2db,您使用 linq 访问数据库,速度非常好,而且 linq 非常容易。并且可以制作 CRUD 功能。

如果这个回答对您有帮助我会很高兴

问候