十进制类型从内存中的 Sqlite 数据库中读取为 double 或 int

Ily*_*nin 5 c# sqlite dapper .net-core

我对 .Net 中的 Sqlite 没有太多经验,但我看到的行为很奇怪。假设我们有一个 .Net 核心应用程序,其中包含以下内容project.json

{
  "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"
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我们还有一个简单的类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; }
}
Run Code Online (Sandbox Code Playgroud)

然后我创建一个内存数据库并用数据填充它(使用 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);
Run Code Online (Sandbox Code Playgroud)

然后我尝试从Items表中读取:

var dbItems = connection.Query<Item>("SELECT Id, Name, Price FROM Items").ToList();
Run Code Online (Sandbox Code Playgroud)

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

未处理的异常:System.InvalidOperationException:解析第 2 列时出错(Price=1.4 - Double)---> System.Invali dCastException:无法将“System.Double”类型的对象转换为“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());
}
Run Code Online (Sandbox Code Playgroud)

结果我得到:

System.Int64 // Price = 3                                                                                                         
System.Double // Price = 1.4
Run Code Online (Sandbox Code Playgroud)

我尝试使用十进制价格在真实数据库上运行查询,返回的数据类型是正确的并且总是十进制(如预期的那样)。
我应该进一步挖掘什么方向?我的内存数据库有问题吗?如何使它与小数一致?

Mol*_*lem 6

在 SQLite 中,如果您创建具有数字/小数列数据类型的表,则它与数字类型关联相关联,并且其默认行为是如果值没有小数则存储整数,或者如果值包含小数则存储实数。这有助于节省字节存储,因为最常用的整数值(中小型应用程序小于一百万个值)需要的字节数比实际数据类型少:

整数(大约):

  • ± 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 数据类型才能被视为实数类型关联,因此即使您将整数发送到 SQLite,它也会存储为实数

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

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

我使用 EF6 和 Dapper 读取/写入数值(来自 SQLite 的整数/实数)并且从未出现错误。

现在,我计划利用 SQLite 数字行为通过简洁的 TypeHandler 来节省磁盘空间,因此我可以复制 SQL ServerMoney实现(在内部,SQL Server 保存一个 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

笔记:

SQLite 可以在低需求的 ASP.NET 应用程序中处理数十个用户,技巧是使用 pragmas 指令和其他东西来调整 SQLite:

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

我还在 C# 方面做了一些增强:

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

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

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

我发现的最好的工具是https://github.com/linq2db/linq2db,您可以使用 linq 来访问数据库,并且 linq 的速度和易用性非常好。并且可以实现CRUD功能。

如果这个答案有帮助我会很高兴

问候


小智 5

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