WPF 学习第四天 --- SQLite 数据库入门与实践
目录
- [什么是 SQLite](#什么是 SQLite)
- [SQLite vs MySQL 对比](#SQLite vs MySQL 对比)
- [安装 SQLite NuGet 包](#安装 SQLite NuGet 包)
- [DB Browser for SQLite 可视化工具](#DB Browser for SQLite 可视化工具)
- [C# 连接 SQLite 核心语法详解](# 连接 SQLite 核心语法详解)
- [DataBaseHelper.cs 完整代码与逐行注释](#DataBaseHelper.cs 完整代码与逐行注释)
- [MainWindow.xaml.cs 中调用数据库的完整代码](#MainWindow.xaml.cs 中调用数据库的完整代码)
- [Monitor_Plc.csproj 中的 SQLite 引用](#Monitor_Plc.csproj 中的 SQLite 引用)
- 常见问题与报错解决
1. 什么是 SQLite
1.1 简介
SQLite 是一个 嵌入式关系型数据库引擎 ,它不是传统的客户端-服务器架构,而是直接把数据库写在一个 .db 文件 中。
┌─────────────────────────────────────┐
│ 你的 C# 程序 │
│ ┌─────────────────────────────────┐ │
│ │ DataBaseHelper │ │
│ │ └── SQLiteConnection ────────►│ │
│ └─────────────────────────────────┘ │
│ │ │
└────────────┼─────────────────────────┘
▼
┌────────────────┐
│ PlcData.db │ ← 一个文件,全部数据都在这里
│ ├─ write_plc │
│ └─ read_plc │
└────────────────┘
1.2 核心特点
| 特性 | 说明 |
|---|---|
| 零配置 | 不需要安装数据库服务、不需要配置端口、不需要管理员权限 |
| 单文件 | 整个数据库就是一个 .db 文件,复制就能带走 |
| 轻量级 | DLL 只有几百 KB,内存占用极低 |
| 无服务器 | 没有独立的数据库进程,直接在程序进程中运行 |
| 标准 SQL | 支持绝大部分 SQL 语法(CREATE、INSERT、SELECT 等) |
1.3 适用场景
| 场景 | 是否适合 |
|---|---|
| ✅ 工控上位机本地存储 | 非常适合,嵌入式设备、PLC 数据记录 |
| ✅ 手机 APP 本地缓存 | Android/iOS 内置 SQLite |
| ✅ 单机桌面软件配置 | 替代 XML/JSON 做配置存储 |
| ✅ 小型网站(访问量低) | SQLite 可以支撑日活几千的小站 |
| ❌ 高并发服务器 | 不适合,并发写性能有限 |
| ❌ 大型企业系统 | 不适合,没有权限管理、主从复制等 |
2. SQLite vs MySQL 对比
2.1 架构对比
SQLite: MySQL:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 程序进程 │ │ 程序进程 │ │ 程序进程 │
│ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │
│ │SQLite│ │ │ │ 连接 │ │ │ │ 连接 │ │
│ │引擎 │ │ │ └──┬───┘ │ │ └──┬───┘ │
│ └──┬───┘ │ │ │ │ │ │ │
└────┼─────┘ └────┼──────┘ └────┼──────┘
▼ ▼ ▼
┌──────────┐ ┌──────────────────────┐
│.db 文件 │ │ MySQL 服务器进程 │
└──────────┘ │ ┌──────────────────┐ │
│ │ 多个数据库文件 │ │
│ └──────────────────┘ │
└──────────────────────┘
2.2 详细对比表
| 对比项 | SQLite | MySQL |
|---|---|---|
| 类型 | 嵌入式数据库 | 客户端-服务器数据库 |
| 安装 | 无需安装,引用 NuGet 包即可 | 需要安装 MySQL Server 服务 |
| 文件 | 单文件(.db) | 多个文件(数据文件、日志文件等) |
| DLL 大小 | ~1.5 MB(System.Data.SQLite.dll) | ~1 GB(MySQL Server 安装包) |
| 内存占用 | 几百 KB ~ 几 MB | 几百 MB ~ 几 GB |
| 端口 | 不使用网络端口 | 默认 3306 端口 |
| 数据库URL | Data Source=PlcData.db;Version=3; |
Server=localhost;Port=3306;Database=plc; |
| 用户/密码 | 不需要 | 需要用户名和密码 |
| 并发写入 | 单线程写(写操作会锁库) | 多线程并发写 |
| 事务 | 支持 | 支持 |
| SQL 语法 | 大部分标准 SQL | 标准 SQL + 存储过程/函数等 |
| 管理工具 | DB Browser for SQLite(轻量) | MySQL Workbench / Navicat |
| 常见用途 | 单机软件、嵌入式设备、移动 APP | Web 服务器、大型业务系统 |
2.3 同样的 SQL,不同的写法
SQLite 特有的语法:
sql
-- SQLite 自动递增主键(AUTOINCREMENT)
CREATE TABLE test (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- 自动编号
name TEXT
);
-- SQLite 获取当前时间的语法
INSERT INTO test VALUES (1, datetime('now', 'localtime'));
MySQL 对应的写法:
sql
-- MySQL 自动递增主键
CREATE TABLE test (
id INT PRIMARY KEY AUTO_INCREMENT, -- 注意是 AUTO_INCREMENT
name VARCHAR(100) -- 需要指定长度
);
-- MySQL 获取当前时间
INSERT INTO test VALUES (1, NOW());
2.4 数据类型对比
| SQLite | MySQL | 说明 |
|---|---|---|
INTEGER |
INT / BIGINT |
整数 |
REAL |
FLOAT / DOUBLE |
浮点数 |
TEXT |
VARCHAR(n) / TEXT |
字符串 |
BLOB |
BLOB |
二进制数据 |
NUMERIC |
DECIMAL |
精确小数 |
关键区别: SQLite 是 弱类型(动态类型) 的------你可以往 INTEGER 列插入 TEXT,它会自动处理。MySQL 是 强类型 的,类型不匹配会报错。但建议还是严格匹配类型。
3. 安装 SQLite NuGet 包
3.1 通过 VS 图形界面安装
- 在 Visual Studio 中 右键点击项目名 → 管理 NuGet 包
- 切换到 "浏览" 标签
- 搜索
System.Data.SQLite - 选择
System.Data.SQLite.Core(注意选 Core 版本) - 点击 安装
3.2 安装后自动生成的文件
安装完成后,VS 会自动修改两个文件:
packages.config(记录安装的包):
xml
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NModbus4" version="2.1.0" targetFramework="net472" />
<package id="System.Data.SQLite.Core" version="1.0.119.0" targetFramework="net472" />
</packages>
Monitor_Plc.csproj(添加了引用和构建步骤):
xml
<!-- SQLite 的 DLL 引用(自动添加) -->
<Reference Include="System.Data.SQLite, Version=1.0.119.0, ...">
<HintPath>..\packages\System.Data.SQLite.Core.1.0.119.0\lib\net46\System.Data.SQLite.dll</HintPath>
</Reference>
<!-- SQLite 的本机运行时(自动添加) -->
<Import Project="..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets" />
3.3 确认安装成功
在代码中添加:
csharp
using System.Data.SQLite;
如果没有报红色波浪线,说明安装成功。
4. DB Browser for SQLite 可视化工具
4.1 下载和安装
DB Browser for SQLite 是一个 免费开源 的 SQLite 数据库管理工具。
官方下载地址: https://sqlitebrowser.org/dl/
对于 Windows 64 位系统,选择:
DB Browser for SQLite - Standard installer for 64-bit Windows
⚠️ 重要:只从官网 sqlitebrowser.org 下载! 不要从第三方下载站下,否则可能下到捆绑广告版。
4.2 基本使用
安装打开后,界面如下:
┌─────────────────────────────────────────────────────────────┐
│ 菜单栏: 文件 / 编辑 / 查看 / 帮助 │
├─────────────────────────────────────────────────────────────┤
│ 工具栏: [打开数据库] [新建数据库] [写入更改] [执行 SQL] │
├──────────┬──────────────────────────────────────────────────┤
│ 左侧面板 │ 右侧主区域 │
│ │ │
│ 📁 plc │ [浏览数据] [结构] [SQL执行] │
│ ├──📄 │ │
│ ├─ read │ id │ created_at │ axis_id │ pos_value │ │
│ ├─ write│ 1 │ 2024-01-15 10:00 │ 1 │ 8.37 │ │
│ │ 2 │ 2024-01-15 10:01 │ 2 │ 12.5 │ │
└──────────┴──────────────────────────────────────────────────┘
4.3 打开你的数据库文件
- 点击 "打开数据库" 按钮
- 导航到:
项目目录\bin\Debug\plcData.db - 打开后可以在 "浏览数据" 标签页看到
read_plc和write_plc两张表
4.4 执行自定义 SQL
点击 "执行 SQL" 标签,可以输入任意 SQL 语句测试:
sql
-- 查询写入历史
SELECT * FROM write_plc;
-- 按轴号统计
SELECT axis_id, COUNT(*) AS 次数 FROM read_plc GROUP BY axis_id;
-- 查询最后 10 条
SELECT * FROM read_plc ORDER BY id DESC LIMIT 10;
4.5 可能出现的报错
打开软件时可能提示 could not get the inetworkconnection instance for the adapter guid,这是 DB Browser for SQLite 的一个已知 bug,与虚拟网卡有关。直接点确定忽略即可,不影响查看数据。
5. C# 连接 SQLite 核心语法详解
5.1 连接字符串(Connection String)
csharp
string connectionString = "Data Source=PlcData.db; Version=3;";
| 部分 | 含义 | 说明 |
|---|---|---|
Data Source=PlcData.db |
数据库文件路径 | 可以是相对路径或绝对路径 |
Version=3; |
SQLite 版本 | 目前都是 3 |
5.2 using 语句块(资源管理)
csharp
// 写法 1:传统 using 块(.NET Framework 4.7.2 用这个)
using (var conn = new SQLiteConnection(connectionString))
{
conn.Open();
// ... 操作数据库 ...
} // ← 离开这个花括号,conn 自动释放(Close + Dispose)
// 写法 2:using 声明(C# 8.0+,需要 .NET Core 3.0+,本项目不能用)
using var conn = new SQLiteConnection(connectionString);
为什么用 using? 因为 SQLiteConnection 是非托管资源 ,必须手动释放。using 保证不管代码是否抛异常,最终都会执行 Dispose()。
5.3 using 块的本质
csharp
// 上下两种写法是等价的
// using 写法(推荐,简洁)
using (var conn = new SQLiteConnection(cs))
{
conn.Open();
}
// try-catch-finally 写法(等价,但啰嗦)
var conn = new SQLiteConnection(cs);
try
{
conn.Open();
}
finally
{
if (conn != null) conn.Dispose(); // 一定会执行
}
5.4 参数化查询(预防 SQL 注入)
❌ 错误写法(字符串拼接------SQL 注入风险):
csharp
// 如果有人输入 axisId = "1; DROP TABLE write_plc;"
// SQL 就变成了 DELETE FROM read_plc WHERE axis_id = 1; DROP TABLE write_plc;
string sql = $"INSERT INTO read_plc (axis_id) VALUES ({axisId})";
✅ 正确写法(参数化------安全):
csharp
using (var cmd = new SQLiteCommand(sql, conn))
{
// @aid 是参数占位符,AddWithValue 给它赋值
cmd.Parameters.AddWithValue("@aid", axisId);
cmd.ExecuteNonQuery();
}
参数化后,即使用户输入了恶意内容,也只会被当作普通字符串值处理,不会影响 SQL 结构。
5.5 可空类型处理
csharp
public void InsertRecord(float? pos) // float? 表示"可能是 float,也可能是 null"
{
using (var cmd = new SQLiteCommand(sql, conn))
{
// (object)pos ?? DBNull.Value 的含义:
// 如果 pos 有值 → 转为 object
// 如果 pos 为 null → 用 DBNull.Value(数据库中的 NULL)
cmd.Parameters.AddWithValue("@pos", (object)pos ?? DBNull.Value);
}
}
?? 运算符(null 合并): a ?? b 如果 a 不是 null 就用 a,否则用 b。
为什么强制转 (object)pos? 因为 float? 是值类型(Nullable),和 DBNull.Value 类型不一致,?? 要求两边类型相同,所以先把 float? 转成 object。
5.6 三张图理解核心流程
创建表的流程:
DataBaseHelper 构造函数
│
▼
创建连接字符串 → new SQLiteConnection(连接字符串)
│
▼
conn.Open() ← 打开数据库连接
│
▼
"CREATE TABLE IF NOT EXISTS ..." ← SQL 建表语句
│
▼
new SQLiteCommand(SQL, conn) ← 创建命令对象
│
▼
cmd.ExecuteNonQuery() ← 执行 SQL(不返回数据,只执行操作)
插入数据的流程:
InsertReadRecord() 被调用
│
▼
new SQLiteConnection(连接字符串) → conn.Open()
│
▼
"INSERT INTO read_plc (...) VALUES (@p1, @p2, ...)" ← SQL 带参数
│
▼
new SQLiteCommand(SQL, conn)
│
▼
cmd.Parameters.AddWithValue("@p1", 值) ← 绑定参数
cmd.Parameters.AddWithValue("@p2", 值)
│
▼
cmd.ExecuteNonQuery() ← 执行插入
数据库文件的位置:
程序编译输出目录(bin\Debug\)
│
▼
AppDomain.CurrentDomain.BaseDirectory
│
▼
E:\...\Monitor_Plc\bin\Debug\plcData.db
6. DataBaseHelper.cs 完整代码与逐行注释
文件位置:NModbus/DataBaseHelper.cs
csharp
// ============================================================
// SQLite 数据库操作帮助类
// 负责:自动创建数据库和表、插入读取记录和写入记录
// ============================================================
// 引入 SQLite 相关的类型(SQLiteConnection, SQLiteCommand 等)
// 注意:必须在项目中安装 System.Data.SQLite.Core NuGet 包后才能用
using System.Data.SQLite;
namespace Monitor_Plc.NModbus
{
/// <summary>
/// 数据库帮助类
/// 封装了 SQLite 的创建表和插入操作
/// </summary>
internal class DataBaseHelper
{
// 数据库连接字符串,在整个类的所有方法中共享使用
// readonly 表示一旦在构造函数中赋值,就不能再修改
private readonly string _connectionString;
/// <summary>
/// 构造函数:创建数据库文件(如果不存在)并创建表(如果不存在)
/// </summary>
public DataBaseHelper()
{
// AppDomain.CurrentDomain.BaseDirectory 获取程序运行目录
// 一般就是 bin\Debug\ 目录
// 数据库文件就放在这个目录下,命名为 plcData.db
string dbPath = AppDomain.CurrentDomain.BaseDirectory + "plcData.db";
// 连接字符串:告诉 SQLite 数据库文件在哪
// Data Source:数据源路径
// Version:SQLite 版本号(目前都是 3)
_connectionString = $"Data Source={dbPath}; Version=3;";
// using 块:确保数据库连接用完后自动释放
// 即使中间抛异常,conn.Dispose() 也会被执行
using (var conn = new SQLiteConnection(_connectionString))
{
// 打开数据库连接(如果 .db 文件不存在,SQLite 会自动创建)
conn.Open();
// SQL 语句:创建两张表(如果还不存在的话)
// @ 符号表示逐字字符串,里面的换行和缩进都会保留
// IF NOT EXISTS 是关键:第二次运行不会覆盖已存在的表
string sql = @"
-- 写入记录表:记录"用户点击写入"时的数据
CREATE TABLE IF NOT EXISTS write_plc (
id INTEGER PRIMARY KEY AUTOINCREMENT,
-- ↑ 主键,自动递增编号(1, 2, 3...)
created_at DATETIME DEFAULT (datetime('now','localtime')),
-- ↑ 创建时间,默认取当前时间(本地时区)
axis_id INTEGER NOT NULL,
-- ↑ 轴号(1~4),不能为空
pos_value REAL,
-- ↑ 位置值(float 对应 SQLite 的 REAL)
vel_value REAL,
-- ↑ 速度值
accel_value REAL,
-- ↑ 加速度值
decel_value REAL
-- ↑ 减速度值
);
-- 读取记录表:记录"用户点击读取"时从 PLC 获取到的数据
CREATE TABLE IF NOT EXISTS read_plc (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME DEFAULT (datetime('now','localtime')),
axis_id INTEGER NOT NULL,
pos_value REAL,
vel_value REAL,
accel_value REAL,
decel_value REAL
);";
// 创建命令对象:将 SQL 发送到数据库执行
using (var cmd = new SQLiteCommand(sql, conn))
{
// ExecuteNonQuery:执行不返回数据集的 SQL
// (CREATE TABLE、INSERT、UPDATE、DELETE 都用这个)
cmd.ExecuteNonQuery();
}
// 离开 using 块时,cmd.Dispose() 自动释放
}
// 离开 using 块时,conn.Close() + conn.Dispose() 自动释放
}
/// <summary>
/// 插入一条"读取"记录
/// 每次用户点击"读取"按钮并成功后调用
/// </summary>
/// <param name="axisId">轴号 (1~4)</param>
/// <param name="pos">位置值(可为 null,如果用户未输入)</param>
/// <param name="vel">速度值</param>
/// <param name="accel">加速度值</param>
/// <param name="decel">减速度值</param>
public void InsertReadRecord(
int axisId,
float? pos,
float? vel,
float? accel,
float? decel)
{
// 每次操作都新建连接,用完就释放(SQLite 轻量级,不必维持长连接)
using (var conn = new SQLiteConnection(_connectionString))
{
// 打开连接
conn.Open();
// INSERT INTO 带参数占位符的 SQL
// @aid, @pos, @vel 等是参数名,前面的 @ 是 SQLite 的参数前缀
string sql = @"INSERT INTO read_plc
(axis_id, pos_value, vel_value, accel_value, decel_value)
VALUES (@aid, @pos, @vel, @accel, @decel)";
using (var cmd = new SQLiteCommand(sql, conn))
{
// 给 SQL 中的参数绑定实际值
// AddWithValue:自动推断参数类型
cmd.Parameters.AddWithValue("@aid", axisId);
// (object)pos ?? DBNull.Value 解读:
// pos 是 float?(可空浮点数)
// (object)pos:把 float? 转为 object(因为 ?? 两边要同类型)
// ?? DBNull.Value:如果 pos 是 null,就用数据库的 NULL
cmd.Parameters.AddWithValue("@pos", (object)pos ?? DBNull.Value);
cmd.Parameters.AddWithValue("@vel", (object)vel ?? DBNull.Value);
cmd.Parameters.AddWithValue("@accel", (object)accel ?? DBNull.Value);
cmd.Parameters.AddWithValue("@decel", (object)decel ?? DBNull.Value);
// 执行 INSERT 语句
cmd.ExecuteNonQuery();
}
}
}
/// <summary>
/// 插入一条"写入"记录
/// 每次用户点击"写入"按钮并成功后调用
/// </summary>
/// <param name="axisId">轴号 (1~4)</param>
/// <param name="pos">位置值</param>
/// <param name="vel">速度值</param>
/// <param name="accel">加速度值</param>
/// <param name="decel">减速度值</param>
public void InsertWriteRecord(
int axisId,
float? pos,
float? vel,
float? accel,
float? decel)
{
// 与 InsertReadRecord 结构完全一致
// 唯一的区别:目标表名是 write_plc 而不是 read_plc
using (var conn = new SQLiteConnection(_connectionString))
{
conn.Open();
// 注意:INSERT INTO write_plc ← 写入记录表
string sql = @"INSERT INTO write_plc
(axis_id, pos_value, vel_value, accel_value, decel_value)
VALUES (@aid, @pos, @vel, @accel, @decel)";
using (var cmd = new SQLiteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@aid", axisId);
cmd.Parameters.AddWithValue("@pos", (object)pos ?? DBNull.Value);
cmd.Parameters.AddWithValue("@vel", (object)vel ?? DBNull.Value);
cmd.Parameters.AddWithValue("@accel", (object)accel ?? DBNull.Value);
cmd.Parameters.AddWithValue("@decel", (object)decel ?? DBNull.Value);
cmd.ExecuteNonQuery();
}
}
}
}
}
7. MainWindow.xaml.cs 中调用数据库的完整代码
以下只展示与数据库相关的部分(完整代码见前几天的文档):
7.1 声明字段和初始化
csharp
public partial class MainWindow : Window
{
private ModbusSerivice _modbus; // Modbus 通信服务
private DataBaseHelper _db; // 数据库助手 ← 新增
public MainWindow()
{
InitializeComponent();
// ... 初始化 AxisItems 列表 ...
try
{
_db = new DataBaseHelper(); // 创建数据库(自动建表)
_modbus = new ModbusSerivice(); // 读取配置
_modbus.Connect(); // 连接 Modbus
MessageBox.Show($"Modbus RTU 连接成功: {_modbus.Comport}");
}
catch (Exception ex)
{
MessageBox.Show($"Modbus 连接失败: {ex.Message}");
}
}
}
7.2 读取按钮中调用(Button_Click_5)
csharp
private void Button_Click_5(object sender, RoutedEventArgs e)
{
if (_modbus == null) { MessageBox.Show("Modbus 未初始化"); return; }
try
{
var axes = AxisItems.ItemsSource as List<AxisItem>;
if (axes == null) return;
// 从 PLC 读取所有轴的数据
_modbus.ReadAllAxes(axes);
// 刷新 UI 显示
AxisItems.ItemsSource = null;
AxisItems.ItemsSource = axes;
// ============ 新增:读取成功后存入数据库 ============
// 遍历 4 个轴,把从 PLC 读到的数据插入 read_plc 表
// i + 1 表示轴号(0→轴1, 1→轴2, 2→轴3, 3→轴4)
for (int i = 0; i < axes.Count; i++)
{
var axis = axes[i];
// float.TryParse:将字符串安全转为 float
// 成功 → 返回 float 值;失败 → 返回 null
// (float?)null 将 null 标记为可空 float 类型
_db.InsertReadRecord(
i + 1, // 轴号(1-based)
float.TryParse(axis.PosValue, out float p) ? (float?)p : null,
float.TryParse(axis.VelValue, out float v) ? (float?)v : null,
float.TryParse(axis.AccelValue, out float a) ? (float?)a : null,
float.TryParse(axis.DecelValue, out float d) ? (float?)d : null
);
}
// =================================================
MessageBox.Show("读取成功");
}
catch (Exception ex)
{
MessageBox.Show($"读取失败: {ex.Message}");
}
}
7.3 写入按钮中调用(Button_Click_6)
csharp
private void Button_Click_6(object sender, RoutedEventArgs e)
{
if (_modbus == null) { MessageBox.Show("Modbus 未初始化"); return; }
try
{
var axes = AxisItems.ItemsSource as List<AxisItem>;
if (axes == null) return;
// 把 UI 上的数据写入 PLC
_modbus.writeAllAxis(axes);
// ============ 新增:写入成功后存入数据库 ============
// 遍历 4 个轴,把写入 PLC 的数据插入 write_plc 表
for (int i = 0; i < axes.Count; i++)
{
var axis = axes[i];
_db.InsertWriteRecord(
i + 1, // 轴号
float.TryParse(axis.PosValue, out float p) ? (float?)p : null,
float.TryParse(axis.VelValue, out float v) ? (float?)v : null,
float.TryParse(axis.AccelValue, out float a) ? (float?)a : null,
float.TryParse(axis.DecelValue, out float d) ? (float?)d : null
);
}
// =================================================
MessageBox.Show("写入成功");
}
catch (Exception ex)
{
MessageBox.Show($"写入失败: {ex.Message}");
}
}
8. Monitor_Plc.csproj 中的 SQLite 引用
项目文件 Monitor_Plc.csproj 中与 SQLite 相关的部分:
xml
<!-- 第 45-47 行:SQLite 的 DLL 引用 -->
<Reference Include="System.Data.SQLite, Version=1.0.119.0, Culture=neutral,
PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
<HintPath>..\packages\System.Data.SQLite.Core.1.0.119.0\lib\net46\System.Data.SQLite.dll</HintPath>
</Reference>
<!-- 第 86 行:源文件引用 -->
<Compile Include="NModbus\DataBaseHelper.cs" />
对应的 packages.config 文件:
xml
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="System.Data.SQLite.Core" version="1.0.119.0" targetFramework="net472" />
</packages>
9. 常见问题与报错解决
Q1: System.Data.SQLite 找不到
❌ 当前上下文中不存在 "SQLiteConnection"
原因: 没有安装 NuGet 包,或者没有加 using
解决:
- 安装
System.Data.SQLite.CoreNuGet 包 - 文件顶部加
using System.Data.SQLite;
Q2: using var conn = new SQLiteConnection(...) 报错
❌ 当前上下文中不存在 "conn"
原因: using var 是 C# 8.0 语法,你的项目是 .NET Framework 4.7.2(C# 7.3)
解决: 改为传统 using 块:
csharp
// ❌ 不支持的写法(C# 8.0+)
using var conn = new SQLiteConnection(cs);
// ✅ 支持的写法(C# 7.3)
using (var conn = new SQLiteConnection(cs))
{
// 在花括号内使用 conn
}
Q3: 三元运算符 ? : 报类型不一致
❌ 无法确定条件表达式的类型,因为 "float" 和 "null" 之间没有隐式转换
原因: float.TryParse 返回 float(值类型,不能 null),但你想返回 float?(可空类型)
csharp
float.TryParse("123", out float p) ? p : null
// ↑ ↑
// float null ← 类型不一致!
解决: 将 p 强制转为 float?(可空 float):
csharp
float.TryParse("123", out float p) ? (float?)p : null
// ↑
// 将 float 提升为 float?
Q4: 数据库文件在哪里?
程序运行后,数据库文件生成在:
项目目录\bin\Debug\plcData.db
可以用 DB Browser for SQLite 打开查看。
Q5: DB Browser for SQLite 启动报网络错误
Could not get the INetworkConnection instance for the adapter guid
这是 DB Browser 的一个已知 bug,与虚拟网卡(如 VSPE、VMware)有关。直接点确定忽略,不影响查看数据库。
Q6: SQLite 需要安装数据库服务吗?
不需要。 SQLite 是嵌入式数据库,没有服务进程,不需要安装、不需要端口、不需要用户名密码。只需要在项目中引用 System.Data.SQLite.Core 这个 NuGet 包即可。
Q7: SQLite 能存多大的数据?
SQLite 默认数据库大小限制为 140TB(理论值),对于工控上位机存储 PLC 数据来说完全够用。单个文件大小没有实际限制。
Q8: 为什么每次操作都新建连接,不用连接池?
csharp
public void InsertReadRecord(...)
{
using (var conn = new SQLiteConnection(...)) // 每次都 new
{
conn.Open();
// ... 用完释放
}
}
SQLite 是进程内数据库,创建连接的开销很小(不像 MySQL/SQL Server 需要网络握手)。每次新建连接的优势是:
- 线程安全:不需要考虑多线程并发问题
- 资源控制:用完就释放,不会占用文件锁
- 代码简单:不需要管理连接池
附录:SQLite 常用 SQL 语句参考
sql
-- ==================== 建表 ====================
-- 创建表(如果不存在)
CREATE TABLE IF NOT EXISTS 表名 (
-- 自增主键(每个表都应该有一个)
id INTEGER PRIMARY KEY AUTOINCREMENT,
-- 默认当前时间
created_at DATETIME DEFAULT (datetime('now','localtime')),
-- 基本类型
name TEXT, -- 字符串
value REAL, -- 浮点数(对应 C# 的 float/double)
count INTEGER, -- 整数(对应 C# 的 int)
data BLOB -- 二进制数据(对应 C# 的 byte[])
);
-- ==================== 增删改查 ====================
-- 插入数据(参数化写法)
INSERT INTO 表名 (列1, 列2) VALUES (@val1, @val2);
-- 查询所有数据
SELECT * FROM 表名;
-- 按条件查询
SELECT * FROM 表名 WHERE axis_id = 1;
-- 按时间排序,取最新 10 条
SELECT * FROM 表名 ORDER BY created_at DESC LIMIT 10;
-- 统计每个轴的数据条数
SELECT axis_id, COUNT(*) FROM 表名 GROUP BY axis_id;
-- 删除数据
DELETE FROM 表名 WHERE id = 1;
-- 删除所有数据(保留表结构)
DELETE FROM 表名;
-- ==================== 与 MySQL 语法差异 ====================
-- SQLite 的自动递增:AUTOINCREMENT
-- MySQL 的自动递增:AUTO_INCREMENT(注意多了 _)
-- SQLite 的当前时间:datetime('now','localtime')
-- MySQL 的当前时间:NOW()
-- SQLite 的随机数:RANDOM()
-- MySQL 的随机数:RAND()
-- SQLite 的字符串拼接:||
-- MySQL 的字符串拼接:CONCAT()
总结
SQLite = 单文件 + 零配置 + 轻量级嵌入式数据库
↓
最适合:单机工控软件、本地数据记录
↓
安装方式:NuGet 包 System.Data.SQLite.Core
↓
管理工具:DB Browser for SQLite(免费开源)
↓
核心类:SQLiteConnection / SQLiteCommand / SQLiteParameter
↓
关键语法:using块 + 参数化查询 + 可空类型处理