# WPF 学习第四天 — SQLite 数据库入门与实践

WPF 学习第四天 --- SQLite 数据库入门与实践

目录

  1. [什么是 SQLite](#什么是 SQLite)
  2. [SQLite vs MySQL 对比](#SQLite vs MySQL 对比)
  3. [安装 SQLite NuGet 包](#安装 SQLite NuGet 包)
  4. [DB Browser for SQLite 可视化工具](#DB Browser for SQLite 可视化工具)
  5. [C# 连接 SQLite 核心语法详解](# 连接 SQLite 核心语法详解)
  6. [DataBaseHelper.cs 完整代码与逐行注释](#DataBaseHelper.cs 完整代码与逐行注释)
  7. [MainWindow.xaml.cs 中调用数据库的完整代码](#MainWindow.xaml.cs 中调用数据库的完整代码)
  8. [Monitor_Plc.csproj 中的 SQLite 引用](#Monitor_Plc.csproj 中的 SQLite 引用)
  9. 常见问题与报错解决

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 图形界面安装

  1. 在 Visual Studio 中 右键点击项目名 → 管理 NuGet 包
  2. 切换到 "浏览" 标签
  3. 搜索 System.Data.SQLite
  4. 选择 System.Data.SQLite.Core(注意选 Core 版本)
  5. 点击 安装

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 打开你的数据库文件

  1. 点击 "打开数据库" 按钮
  2. 导航到:项目目录\bin\Debug\plcData.db
  3. 打开后可以在 "浏览数据" 标签页看到 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

解决:

  1. 安装 System.Data.SQLite.Core NuGet 包
  2. 文件顶部加 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块 + 参数化查询 + 可空类型处理