您没有指出您使用的是哪个 DB 提供程序,但标准提供程序(来自 SQLite 开发人员)将看到 Integer 并将数据映射到不允许小数的 NET Int32 类型。 Real 会像 Decimal 一样保存小数。
there is no other type in SQLite. there is only "Text", "Integer", "Real" and "Blob"
确实如此,但它适用于 SQLite DB,而不是 DB Provider。标准 DB Provider 被巧妙地编写为能够将 4 种基本类型转换为各种 NET 类型,从而使实际的存储类型/格式成为实现细节。
提供程序代码包括许多步骤、查找表、子系统、字典和执行转换的方法。甚至还有一种方法可以定义自定义类型名称。以下是对工作原理的概括解释。
SQLite NET 提供程序识别的列类型名称
字节,SByte
INT8、INTEGER8、TINYSINT (SByte)
UINT8、UNSIGNEDINTEGER8、TINYINT(字节)
积分(短、长、有符号、无符号等)
BIGINT, BIGUINT, 计数器, IDENTITY, INT, INT16, INT32, INT64, INTEGER, INTEGER16, INTEGER32, INTEGER64, LONG, SMALLINT, SMALLUINT, UINT, UINT16, UINT32, UINT64, ULONG, UNSIGNEDINTEGER, UNSIGNEDINTEGER16, UNSIGNEDINTEGER32, UNSIGNEDINTEGER64 >
布尔值
位、布尔值、布尔值、逻辑、是否
文本/字符串
CHAR、CLOB、LONGCHAR、LONGTEXT、LONGVARCHAR、MEMO、NCHAR、NOTE、NTEXT、NVARCHAR、STRING、TEXT、VARCHAR、VARCHAR2
数字
双,浮动,真实; SINGLE(单人)
十进制
货币、十进制、货币、数字、数字
BLOB
二进制、BLOB、通用、图像、OLEOBJECT、RAW、VARBINARY
日期/时间
日期、日期时间、SMALLDATE、时间、时间戳
GUID
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 相关联,DbType 用于确定要返回的 NET 数据类型。该列表足够全面,它可以为您转换除 1 或 2 种类型之外的所有类型。
例如,请注意GUID 和IMAG* 都存储为BLOB,但GUID 类型名称与不同的DbType 相关联,它允许that BLOB 的返回方式与IMAGE BLOB 不同。
您还可以通过连接对象指定类型。空间和范围不允许解释,但虽然有点乏味,但它允许您为自定义类型名称提供数据类型。
存储数据
在存储数据时,您不必担心应该如何存储它。 DB Provider 将使用传递的DbType 来查找要使用的SQLite 类型(Affinity")。如果您使用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。无需传递特定格式的字符串。提供者知道事情。 (请参阅下面的日期时间选项。)
- 要仅保存日期,请仅传递日期:
.Value = DateTime.Now.Date。
两个不同的查找表用于保存和读取数据,它们的共同点是DbType,这就是为什么它很重要。使用正确的可以确保数据可以进行往返。避免使用AddWithValue。
演示/结果
来自 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;
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,但返回的方式不同。 Active (BOOL) 和 NotActive (YESNO) 使用不同的类型名称,但返回相同的数据类型。一切都按预期进行。
日期时间“问题”和选项
TIME 作为列类型名称并不能按预期工作。它不会将DateTime.Now.TimeofDay (Timespan) 解析为它。该表将 TIME 映射到 DbType.DateTime。
请勿使用 DbType.DateTime2 或 .DateTimeOffset。这些在转换器查找中丢失,因此数据以无效格式(版本 1.0.103.0)存储为文本。
UTC、种类和标志
SQLite NET Provider 不只支持一种日期格式。保存为 UTC 时,数据包含一个指示符。但是,无论保存为本地还是 UTC,Kind 始终返回为Unspecified。对此的部分补救措施是将datetimekind 添加到您的连接字符串中:
`...;datetimekind=Utc;`
`...;datetimekind=Local;`
这将为所有DateTime 值设置Kind返回,但不转换值。
this 的补救措施是使用(相对)新的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 仍然未指定。这显然是由于DataColumn 中的DateTimeMode 属性,并且可能是Microsoft 的设计决定不假设列中的所有日期将始终相同Kind。这也体现在其他数据库中。
当使用 UTC 或本地连接时,提供者会单独保留未指定(这也适用于查询中的日期)。所以不应该有任何不希望的额外转换:在更新中再次不会转换在DataTable 中读取和“伪装”为未指定的 UTC 日期。
刻度格式
与“传统智慧”相反,日期并不总是仅保存为文本;为了节省一点空间,您可以保存刻度值。由于这些不能有时区指示器,Kind 相关选项可能非常有用。要启用 Ticks,请使用 DateTimeFormat 连接字符串选项:
Private LiteConnStr = "...;datetimekind=Utc;DateTimeFormat=Ticks;..."
'e.g: 634939900800000000
其他DateTimeFormat 选项包括CurrentCulture、ISO8601(默认)、JulianDay 和UnixEpoch。无需更改列类型名称即可使用其中一种格式。这仍然是一个日期,SQLite Provider 根据连接标志处理实现细节。
用户界面浏览器
许多 SQLite UI 浏览器似乎只知道这四种规范类型。也许这是有意为之,但这限制了它们对 NET 开发人员的用处,并隐藏了 NET 提供者的功能。
SQLiteStudio(版本:3.1.0)提供了更多,但它似乎不知道完整列表,因为缺少一些非常有用的列表(例如 GUID、IMAGE、SINGLE、整数变体)。
它确实允许你输入任何你想要的类型名称,所以获利!
摘要(tl;dr)
- NET Provider 通过支持多种列类型名称,为 SQLite 添加了功能,
- 每个受支持的名称都与一个
DBType 相关联,该DBType 决定了实际的返回数据类型
- 在保存数据时使用正确的
DbType 可确保数据进行往返
- NET Provider 将为您执行大部分转换
-
DateTimeKind 和 BindDateTimeWithKind 选项允许自动、统一的时区存储日期
最重要的是,NET 提供者将实际存储作为实现细节。