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

时间:2016-10-16 21:18:41

标签: 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"
    }
  }
}

我们还有一个简单的课程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 - 双倍)---&gt; System.Invali dCastException:   无法投射“System.Double”类型的对象。输入&#39; System.Int64&#39;。

好的,然后我尝试使用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

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

2 个答案:

答案 0 :(得分:4)

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

整数(约):

  • ±128&gt; 1字节(2 ^ 8-1)
  • ±32,768&gt; 2个字节(2 ^ 16-1)
  • ±8,388,608&gt; 3个字节(2 ^ 24-1)
  • ±2,147,483,648&gt; 4个字节(2 ^ 32-1)
  • ±140,737,488,355,328&gt; 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

我知道真实可能是不精确的,但我在wal模式下使用C#Decimal数据类型和SQLite与磁盘中的数据库进行了一些测试,如果所有操作都是在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应用程序中处理数十个用户,诀窍是使用pragma指令和其他东西调整SQLite:

  • WAL模式:有助于管理更好的并发性和性能
  • 页面大小:如果匹配文件系统的群集大小,则可以提高性能
  • 共享缓存模式:当在多个线程中加入时,实现表锁而不是数据库锁
  • Datetime UnixEpoch:保存秒(4个字节)而不是文本ISO datetime(“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功能。

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

问候

答案 1 :(得分:3)

这是SQLite的一项功能,请参阅此链接:https://sqlite.org/faq.html#q3