vb.net - SQLite不能正确存储小数

标签 vb.net sqlite datagridview

我有一个带有名为tbl_invent的表的sqlite DB,在窗体加载时,它用表中的内容填充了datagridview。问题是我有字段名cost和sell_price,它们的字段都带有小数点,并且在加载表格时它仅显示数字而不是小数。

样品:

表格= 1.75,DGV = 1.00

   Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    connect()
    Dim da As New SQLiteDataAdapter("select * from tbl_Invent", connection)
    Dim ds As New DataSet
    da.Fill(ds, "tbl_Invent")
    DataGridView1.DataSource = ds
    DataGridView1.DataMember = "tbl_Invent"
    DataGridView1.Columns(6).ValueType = GetType(Single)
    DataGridView1.Columns(6).DefaultCellStyle.Format = "N2"
    DataGridView1.Columns(7).ValueType = GetType(Single)
    DataGridView1.Columns(7).DefaultCellStyle.Format = "N2"

    connection.Close()
    da.Dispose()
End Sub


我已经检查了正确的字段类型“整数”,我也尝试了“ GetType(Single)”和“ GetType(Decimal)”,但仍然相同。谁能指出我正确的方向?谢谢。



来自评论:

SQLite中没有其他类型。在SQLite中也只有“ Text”,“ Integer”,“ Real”和“ Blob”,它表示整数可以有小数。

最佳答案

您没有指出正在使用的数据库提供程序,但是标准提供程序(来自SQLite开发人员)将看到Integer并将数据映射到不允许小数的NET Int32类型。 Real将保存小数,与Decimal相同。

there is no other type in SQLite. there is only "Text", "Integer", "Real" and "Blob"

是的,但这适用于SQLite数据库,而不适用于数据库提供程序。标准的DB Provider巧妙地编写为能够将4种基本类型转换为各种NET类型,从而使实际的存储类型/格式成为实现细节。



提供者代码包括许多步骤,查找表,子系统,字典和执行转换的方法。甚至还有一种定义自定义类型名称的方法。以下是工作原理的一般说明。

SQLite NET Provider识别的列类型名称

字节,字节
INT8,INTEGER8,TINYSINT(字节)
UINT8,UNSIGNEDINTEGER8,TINYINT(字节)

整数(短,长,有符号,无符号等)
BIGINT,BIGUINT,COUNTER,IDENTITY,INT,INT16,INT32,INT64,INTEGER,INTEGER16,INTEGER32,INTEGER64,LONG,SMALLINT,SMALLUINT,UINT,UINT16,UINT32,UINT64,ULONG,UNSIGNEDINTEGER,UNSIGNEDINTEGEREGER16,UN64EDINTEDEGEREGER16,UN64

布尔型
BIT,布尔,布尔,逻辑,是

文字/字串
CHAR,CLOB,LONGCHAR,LONGTEXT,LONGVARCHAR,MEMO,NCHAR,NOTE,NTEXT,NVARCHAR,STRING,TEXT,VARCHAR,VARCHAR2

数字
双重,浮动,真实;单(单)

小数
货币,十进制,货币,数字,数字

BLOB
二进制,BLOB,常规,图像,OLEOBJECT,RAW,VARBINARY

约会时间
DATE,DATETIME,SMALLDATE,TIME,TIMESTAMP

图形用户界面
GUID,唯一身份验证者

来源:SQLiteDbTypeMap中的SQLiteConvert.cs(版本1.0.103; 2016年9月)。

本质上,DBProvider以适当的SQLite类型存储数据,但在回读它时,它将使用表定义中使用的类型将数据转换回NET类型。 SQLite提供程序包括一个大型SQLiteConvert类,可以为您完成所有转换。



尽管对于SQLite奉献者来说这似乎是常识,但我找不到狂野记载的文档。大多数站点只是重新格式化SQLite网站内容。它可能记录在帮助文件中,但是我的主题没有内容。根据列表,很容易意外使用有效名称并发现其有效。

该列表合并了其他DB使用的最常见的符号,以及一些NET类型。例如,可以将Boolean定义为BIT, BOOL, BOOLEAN, LOGICAL or YESNO。因此,此表定义是合法的并且具有完整功能:

CREATE TABLE LiteColTypes (
    Id        INTEGER     PRIMARY KEY AUTOINCREMENT,
    Name      TEXT,
    ItemDate  DATETIME,
    Char3     CHAR (3),
    UINT32    UINT32,
    Value     INT16,
    VarChar5  VARCHAR (5),
    GCode     GUID,
    Price     DECIMAL,
    ItemImg   IMAGE,
    Active    BOOL,
    NotActive YESNO
);


有几件事要注意,还有一些有用的DateTime选项。

这个怎么运作

该列表来自以下代码:

/// <summary>
/// Builds and returns a map containing the database column types
/// recognized by this provider.
/// </summary>
/// <returns>
/// A map containing the database column types recognized by this
/// provider.
/// </returns>
private static SQLiteDbTypeMap GetSQLiteDbTypeMap()
{
return new SQLiteDbTypeMap(new SQLiteDbTypeMapping[] {
    new SQLiteDbTypeMapping("BIGINT", DbType.Int64, false),
    new SQLiteDbTypeMapping("BINARY", DbType.Binary, false),
    new SQLiteDbTypeMapping("BIT", DbType.Boolean, true),
    new SQLiteDbTypeMapping("BLOB", DbType.Binary, true),
    new SQLiteDbTypeMapping("BOOL", DbType.Boolean, false),
    new SQLiteDbTypeMapping("BOOLEAN", DbType.Boolean, false),
    ...
    new SQLiteDbTypeMapping("GUID", DbType.Guid, false),
    new SQLiteDbTypeMapping("IMAGE", DbType.Binary, false)
    ... (many more)


XML注释被保留,因为它具有启发性和权威性:


构建并返回一个包含此提供程序识别的数据库列类型的映射。
(强调我的)。


DbType对于该过程至关重要。

读取数据

上面的SQLiteDbTypeMap将它识别的许多许多列名称与DbType关联,该列用于确定要返回的NET数据类型。该列表足够全面,可以为您转换除1或2种类型之外的所有类型。

例如,请注意GUIDIMAG *都存储为BLOB,但是GUID类型名称与不同的DbType关联,这使得BLOB的返回不同于IMAGE BLOB的返回。 。

您还可以通过连接对象指定类型。空格和作用域不允许解释,但是虽然有点乏味,但它允许您为自定义类型名称提供数据类型。

储存资料

存储数据时,您不必担心如何存储数据。 DB Provider将使用传递的DbType来查找要使用的SQLite类型(“相似性”)。如果使用AddWithValue或(过时的)Add(object, object)重载,则DBProvider会猜测该类型。猜测,但是不要那样做。

因此,不需要此转换:

cmd.Parameters.Add("@g", DbType.Binary).Value = myGuid.ToByteArray();


使用与任何其他数据库相同的代码:

' // add trailing semicolons for c#
cmd.Parameters.Add("@n", DbType.String).Value = "Ziggy"
cmd.Parameters.Add("@dt", DbType.DateTime).Value = DateTime.Now 
cmd.Parameters.Add("@c3", DbType.StringFixedLength, 3).Value = "XYZ123" '// see notes
cmd.Parameters.Add("@u", DbType.UInt16).Value = 3
cmd.Parameters.Add("@g", DbType.Guid).Value = myGuid
    cmd.Parameters.Add("@p", DbType.Decimal).Value = 3.14D

'// 'ToByteArray()' is an extension method to convert
cmd.Parameters.Add("@img", DbType.Binary).Value = myImg.ToByteArray()
cmd.Parameters.Add("@act", DbType.Boolean).Value = True


笔记:


使用DbType描述传递的数据,而不是您认为应如何保存(例如DbType.Guid,而不是Binary表示Guid)。提供者将执行大多数转化。
没有DbType.Image,因此需要字节数组转换。
Char()/VarChar()字段指定大小不会限制保存的字符数。这似乎是一个错误,因为保存的字符数超过定义的字符数可能会阻止该行加载。
UInt16的作用相反:尝试传递超出范围的值,例如UInt16的-5,将导致Overflow Exception。但是对于已存储的值,它将返回65531
列的大小/精度参数(例如Decimal(9,2))似乎无关紧要。内部表提供固定的精度和大小。
对于日期,请传递日期并指示DbType.DateTime。永远都不需要传递特定格式的字符串。提供者知道事情。 (请参阅下面的DateTime选项。)
要仅保存日期,请仅传递日期:.Value = DateTime.Now.Date


两个不同的查询表用于保存和读取数据,它们的共同点是DbType,这就是为什么它很重要。使用正确的数据可确保数据可以往返。避免使用AddWithValue

演示/结果

enter image description here
来自UI浏览器的数据视图

加载数据不需要任何特殊操作:

 // Dim SQL = "SELECT * FROM LiteColTypes"   ' for VB
 string SQL = "SELECT * FROM LiteColTypes";      
 ...
 dbCon.Open();
 Dim dt As New DataTable();
 dt.Load(cmd.ExecuteReader());
 dgv.DataSource = dt;


enter image description here
DataGridView中的相同数据

DGV可以正确识别并显示GUID,图像和布尔值列。每个DataColumn的数据类型均符合预期:


       Name --->    System.String (maxLen = 2147483647)  
   ItemDate --->  System.DateTime  
      Char3 --->    System.String (maxLen = 3)  
     UINT16 --->    System.UInt16  
   VarChar5 --->    System.String (maxLen = 5)  
      GCode --->      System.Guid  
      Price --->   System.Decimal  
    ItemImg --->    System.Byte[]  
     Active --->   System.Boolean  
  NotActive --->   System.Boolean  



请注意,Guid和Image项都存储为BLOB,但返回的方式不同。活动(BOOL)和非活动(YESNO)使用不同的类型名称,但返回相同的数据类型。一切都按要求工作。

DateTime“问题”和选项

TIME作为列类型名称不能按预期方式工作。它不会解析DateTime.Now.TimeofDayTimespan)。该表将TIME映射到DbType.DateTime

不要使用DbType.DateTime2.DateTimeOffset。这些在转换器查找中丢失,因此数据以无效格式(版本1.0.103.0)存储为文本。

UTC,种类和标志

SQLite NET提供程序不只支持一种日期格式。当另存为UTC时,数据包括一个指示符。但是,无论保存为本地还是UTC,Kind始终返回为Unspecified。解决此问题的方法之一是将datetimekind添加到您的连接字符串中:

`...;datetimekind=Utc;`
`...;datetimekind=Local;`   


这将为所有返回的Kind值设置DateTime,但不转换该值。

解决方法是使用(相对)新的BindDateTimeWithKind连接标志。保存时,它将转换日期以匹配连接的DateTimeKind

Private LiteConnStr = "Data Source='C:\Temp\demo.db';Version=3;DateTimeKind=Utc;"
...
Dim dt As New DateTime(2011, 2, 11, 11, 22, 33, 444, DateTimeKind.Local)

Using dbCon = New SQLiteConnection(LiteConnStr)
    dbCon.Flags = SQLiteConnectionFlags.Default Or 
                  SQLiteConnectionFlags.BindDateTimeWithKind
    ...
    cmd.Parameters.Add("@dt", DbType.DateTime).Value = dt

    ' == 2011-02-11 17:22:33.444Z   note the hour


尽管已传递本地日期,但BindDateTimeWithKind会将其保存为UTC以匹配连接。由于“ DateTimeKind = Utc;”而返回UTC日期。连接设置。

请注意,DateTimeKind用于读取的日期,而BindDateTimeWithKind在保存日期时起作用。个别而言,它们似乎会使情况更糟。整个数据库一起成为基于UTC(或本地)的日期,日期统一保存并读取为同一Kind -您无需执行任何操作。

ConnectionFlags手动操作很麻烦,要在连接字符串中指定它们:

connx = "...;datetimekind=Utc;flags='Default, BindDateTimeWithKind';"


限制/问题

统一的Kind处理对于DbDataReader至少适用于Dapper。但是,当使用DataTable时,日期的Kind仍未指定。显然,这是由于DateTimeMode中的DataColumn属性引起的,并且可能是Microsoft的一项设计决策,即不假定列中的所有日期始终都是相同的Kind。这也体现在其他数据库中。

使用UTC或本地连接时,提供程序将不指定未指定(这也适用于查询中的日期)。因此,不应有任何不希望有的额外转换:在DataTable中读取并“伪装”为未指定的UTC日期不会在更新中再次转换。

报价格式

与“传统智慧”日期相反,日期并不总是且仅保存为TEXT。为了节省一点空间,您可以保存刻度值。由于这些不能具有时区指示器,因此Kind相关选项可能非常有用。要启用Ticks,请使用DateTimeFormat连接字符串选项:

Private LiteConnStr = "...;datetimekind=Utc;DateTimeFormat=Ticks;..."
'e.g: 634939900800000000


其他DateTimeFormat选项包括CurrentCulture,ISO8601(默认设置),JulianDay和UnixEpoch。无需更改列类型名称即可使用这些格式之一。现在仍然是日期,SQLite Provider根据连接标志处理实现细节。

UI浏览器

许多SQLite UI浏览器似乎只知道四种规范类型。也许这是故意的,但这限制了它们对NET开发人员的实用性,并隐藏了NET提供程序的功能。

SQLiteStudio(版本:3.1.0)提供了更多功能,但它似乎并不知道完整列表,因为缺少了一些非常有用的功能(例如GUID,IMAGE,SINGLE,整数变体)。

它确实允许您输入所需的任何类型名称,因此,Profit!

摘要(tl; dr)


NET Provider通过支持各种类型的列名称来为SQLite添加功能,
每个受支持的名称都与DBType关联,该DbType确定实际的返回数据类型
保存数据时使用正确的DateTimeKind可确保数据往返
NET Provider将为您执行大多数转换
BindDateTimeWithKind选项允许自动,统一的TimeZone存储日期


首先,NET提供程序使实际存储成为实现细节。

关于vb.net - SQLite不能正确存储小数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44298684/

相关文章:

vb.net - If() 语句的奇怪行为

vb.net - 在 VB.NET 中检测非英语按键

android - 如何在android中加密和解密我的应用程序数据库

mysql - 如何将Datagridview中当前选定的行插入到数据库

regex - 正则表达式查找引号之间的值

c# - 在 VB.NET 中声明一个十六进制常量

java - android.database.CursorIndexOutOfBoundsException : Index -1 requested, 大小为 22

java - 使用 CURRENT_TIMESTAMP 作为 ORMLite 和 SQLite 字段的默认值

xml - 错误十六进制值0x00是无效字符,通过LAN发送XML格式的数据表时

c# - datagridview 事件的枚举索引