# 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_plcwrite_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块 + 参数化查询 + 可空类型处理