简体   繁体   English

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

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

I don't have too much experience with Sqlite in .Net, but the behaviour I see is rather strange. 我对.Net中的Sqlite没有太多经验,但我看到的行为相当奇怪。 Let's say we have a .Net core app with following project.json : 假设我们有一个带有以下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"
    }
  }
}

Also we have a simple class Item : 我们还有一个简单的类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; }
}

Then I create an in-memory database and populate it with data (using Dapper): 然后我创建一个内存数据库并用数据填充它(使用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);

Then I try to read from the Items table: 然后我尝试从Items表中读取:

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

When I run the solution, I get the following exception: 当我运行解决方案时,我得到以下异常:

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'. 未处理的异常:System.InvalidOperationException:解析第2列时出错(Price = 1.4 - Double)---> System.Invali dCastException:无法将类型为'System.Double'的对象强制转换为'System.Int64'。

Ok, then I tried to use Microsoft.Data.Sqlite to get the data: 好的,然后我尝试使用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());
}

As a result I get: 结果我得到:

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

I tried running query on real database with decimal price, the data type returned is correct and is always decimal (as expected). 我尝试使用十进制价格在真实数据库上运行查询,返回的数据类型是正确的,并且始终是十进制的(如预期的那样)。
What direction should I dig further? 我应该进一步挖掘什么方向? Is something wrong with my in-memory database? 我的内存数据库有问题吗? How to make it consistent with decimals? 如何使其与小数一致?

In SQLite, if you create a Table with a Numeric/Decimal Column datatype, it's associated to Numeric type affinity and its default behavior is to store an integer if a value has no decimals, or to store a real number if value contains decimals. 在SQLite中,如果创建具有数字/十进制列数据类型的表,则它与数值类型相关性关联 ,其默认行为是在值没有小数时存储整数,或者如果值包含小数则存储实数。 This helps to save bytes storage because the most used integer values (less than a million value for small to medium applications) needs less bytes than real data type: 这有助于节省字节存储,因为最常用的整数值(小到中等应用程序的值不到一百万)需要的字节数比实际数据类型少:

Integer (approx): 整数(约):

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

Against 8 byte real IEEE datatype 反对8字节真实IEEE数据类型

If you need to store a real number always, it's necessary to declare a float/real datatype in order to be considered as real type affinity, thus even if you send an integer number to SQLite it would be stored as real 如果你总是需要存储一个实数,就必须声明一个float / real数据类型,以便被认为是真正的类型亲和性,因此即使你向SQLite发送一个整数,它也会被存储为真实的

Source: https://sqlite.org/datatype3.html 资料来源: https//sqlite.org/datatype3.html

I know real could be imprecise, but I've made some test using C# Decimal datatype and SQLite with database in disk in wal mode, and I've never had a rounding error if all operations are made in C#. 我知道真实可能是不精确的,但我在wal模式下使用C#Decimal数据类型和SQLite与磁盘中的数据库进行了一些测试,如果所有操作都是在C#中进行的,我从未遇到过舍入错误。 But when I made some calculations directly in SQLite I got some rounding errors and some bad calculations because of 2 integer division 但是当我直接在SQLite中进行一些计算时,由于2整数除法,我得到了一些舍入误差和一些不好的计算

I use EF6 and Dapper to read/write Numeric values (integer/real from SQLite) and never got an error. 我使用EF6和Dapper读取/写入数值(来自SQLite的整数/实数)并且从未出现错误。

Now, I'm planning to take advantage of SQLite numeric behavior to save disk space with dapper TypeHandler, thus I may replicate SQL Server Money implementation (internally, SQL Server saves an 8 byte integer and it's divided by 10000 when it loads into memory): 现在,我打算利用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;
    }
}

Additionally, I set up datetime UnixEpoch implementation to save diskspace and enhance performance 另外,我设置了datetime UnixEpoch实现来节省磁盘空间并提高性能

Note: 注意:

SQLite may handle dozens of users in a low demand asp.net application, the trick is tune-up SQLite with pragmas directives and other things: SQLite可以在低需求的asp.net应用程序中处理数十个用户,诀窍是使用pragma指令和其他东西来调整SQLite:

  • WAL mode: helps to manage better concurrency and performance WAL模式:有助于管理更好的并发性和性能
  • Page size: if matches the file system's cluster size, enhances performance 页面大小:如果匹配文件系统的簇大小,则可以提高性能
  • Shared Cache Mode: implements table locks instead database locks when it's acceded in several threads 共享缓存模式:在多个线程中加入时,实现表锁而不是数据库锁
  • Datetime UnixEpoch: saves seconds (4 bytes) instead of text ISO datetime ("2012-12-21T17:30:24" = 20 bytes). Datetime UnixEpoch:保存秒(4个字节)而不是文本ISO datetime(“2012-12-21T17:30:24”= 20个字节)。
  • Cache size: saved I/O access keeping last accessed pages in memory 高速缓存大小:保存的I / O访问保留最后访问的页面在内存中

Also I made some enhancements in C# side: 我也在C#方面做了一些改进:

  • I made a custom DateTime struct for DateTime values in order to save data as integer overriding ToString method and loads into memory as DateTime with constructor overload. 我为DateTime值创建了一个自定义DateTime结构 ,以便将数据保存为整数重写ToString方法,并将其作为DateTime加载到内存中,并带有构造函数重载。
  • I Made a custom Numeric struct in C#, (like DateTime one), to save money as cents overriding ToString method and loads into memory as Decimal with constructor overload 我在C#中创建了一个自定义数字结构(比如DateTime一个),以节省资金作为重写ToString方法的美分,并以构造函数重载的方式加载到内存中

avoid save unnecessary data (Error logs, email html text) 避免保存不必要的数据(错误日志,电子邮件html文本)

I think you should use an ORM like EFx or Dapper if you plan to use SQLite as backend. 我认为如果你计划使用SQLite作为后端,你应该使用像EFx或Dapper这样的ORM。 With dapper you would need to create your own micro framework to save development time (basic CRUD functionality and Linq queries transformation). 使用精巧的工具,您需要创建自己的微框架以节省开发时间(基本的CRUD功能和Linq查询转换)。

The best tool that I´ve found is https://github.com/linq2db/linq2db , you use linq for accessing database with very good speed and easiness of linq. 我找到的最好的工具是https://github.com/linq2db/linq2db ,你使用linq以非常好的速度和linq的容易性访问数据库。 And it is possible to make CRUD functionality. 并且可以制作CRUD功能。

I'll be happy if this answer helps 如果这个答案有帮助,我会很高兴

regards 问候

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM