【问题标题】:Decimal type is read as double or int from In-memory Sqlite database从 In-memory Sqlite 数据库中读取十进制类型为 double 或 int
【发布时间】:2017-04-14 22:37:48
【问题描述】:

我对 .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"
    }
  }
}

我们还有一个简单的类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 表中读取:

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

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

未处理的异常:System.InvalidOperationException:解析错误 第 2 列(价格 = 1.4 - 双倍)---> 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());
}

结果我得到:

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

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

【问题讨论】:

    标签: c# sqlite dapper .net-core


    【解决方案1】:

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

    整数(大约):

    • ± 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 数据类型

    如果您需要始终存储实数,则必须声明浮点/实数数据类型才能被视为实类型亲和性,因此即使您将整数发送到 SQLite,它也会被存储为实数

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

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

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

    现在,我计划利用 SQLite 数字行为来使用 dapper TypeHandler 节省磁盘空间,因此我可以复制 SQL Server Money 实现(在内部,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;
        }
    }
    

    另外,我设置了 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# 中创建了一个自定义 Numeric 结构(如 DateTime 之一),以节省成本,因为它覆盖 ToString 方法并作为 Decimal 加载到内存中并带有构造函数重载

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

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

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

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

    问候

    【讨论】:

    • 我忘了说,我看过net core还不成熟,不能用于生产环境,还有很多bug还没解决
    【解决方案2】:

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

    【讨论】:

    • 哇,感谢您的链接,将会阅读。那么这是否适用于插入和选择?我确信当我使用真实的数据库文件并在那里使用 decimal 字段时,它会按预期读取(十进制),没有错误。我现在很困惑……
    • SQLite 会将示例中的第一个值存储为“3”,而不是“3.0”或“3m”。当它读取它时,由于动态类型是假定值是一个 int。真正的数据库具有强类型数据,因此它不做任何假设,并将其返回为十进制。
    猜你喜欢
    • 2019-09-27
    • 1970-01-01
    • 2013-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-22
    • 1970-01-01
    • 2019-03-09
    相关资源
    最近更新 更多