1. 项目概述与核心价值如果你是一名C#开发者手头有一些本地数据需要管理——比如从传感器采集的日志、个人记账记录或者一个小型库存清单——你大概率不想为了这点数据去折腾一个庞大的SQL Server或者MySQL。这时候SQLite就成了一个绝佳的选择它就是一个文件无需安装数据库服务器直接嵌入在你的应用里随用随走。而要把这些数据以清晰、可交互的方式呈现给用户WinForms的DataGridView控件几乎是桌面端最直接、最高效的解决方案。这个组合即C# WinForms SQLite构成了无数中小型桌面应用尤其是工业监控、实验室数据采集、个人工具软件的数据后台与展示核心。我见过不少新手朋友一提到数据库和GUI结合就发怵觉得要配置连接字符串、处理ORM、管理连接池步骤繁琐。实际上利用ADO.NET中为SQLite量身定做的System.Data.SQLite库配合WinForms成熟的数据绑定机制从零搭建一个能读、能看、甚至能改的数据库GUI应用核心代码可能不超过50行。本指南的目的就是帮你捅破这层窗户纸让你在半小时内亲手搭建一个可以浏览本地SQLite数据库文件的桌面程序。无论你是学生需要完成课程设计还是工程师需要快速原型验证一个数据展示工具这套技术栈都能让你事半功倍。2. 开发环境搭建与项目创建2.1 工具选型为什么是Visual Studio Community Edition工欲善其事必先利其器。对于C# WinForms开发Visual StudioVS依然是无可争议的首选尤其是其免费的Community版本。这里的选择理由很实在第一它提供了业界最优秀的WinForms窗体设计器真正的“拖拽式”开发。你不需要手写一行布局XML或代码就能通过鼠标拖放按钮、文本框、表格控件并直观地设置它们的属性。这对于快速构建界面、专注于业务逻辑至关重要。第二它深度集成了NuGet包管理器我们后续安装SQLite支持库只需要点几下鼠标。第三它对.NET生态的支持最完整无论是传统的.NET Framework还是现代的.NET 6/8项目模板和调试工具都一应俱全。注意在安装Visual Studio时务必在安装程序的工作负载选择页面勾选“.NET桌面开发”。这个工作负载会自动包含WinForms、WPF项目模板以及基础的.NET SDK避免后续单独配置的麻烦。2.2 创建WinForms项目框架版本的选择策略打开VS后点击“创建新项目”搜索“Windows窗体应用”。这里你会看到两个主要选项“Windows窗体应用(.NET Framework)”和“Windows窗体应用”。前者基于传统的、全功能的.NET Framework如4.7.2, 4.8后者基于跨平台的.NET如.NET 6, 8现在官方称为“.NET”去掉了Core。如何选择选择“.NET”如.NET 8这是微软当前主推的方向。如果你的应用是全新的且未来有可能考虑跨平台虽然WinForms本身在非Windows平台支持有限但底层库是跨平台的或者希望获得最新的性能优化和语言特性请选这个。它更轻量部署方式更灵活可以发布为独立单体文件。选择“.NET Framework”如果你的目标机器是旧的Windows系统如某些工控机仍使用Windows 7或者你需要依赖一些仅支持完整.NET Framework的第三方老库那么选择这个更稳妥。在本指南中我推荐使用**.NET 8长期支持版本**因为它代表了未来且对于SQLite的操作完全兼容。创建项目时给你的项目起个名字比如SqliteViewer选择好存放位置点击创建。VS会自动生成一个包含Form1.cs和Form1.Designer.cs的初始项目。Form1.cs是你编写逻辑代码的地方而Form1.Designer.cs是设计器自动生成的界面布局代码通常不需要手动修改。2.3 引入SQLite支持通过NuGet安装核心库项目创建好后我们需要让项目具备操作SQLite数据库的能力。这通过NuGet包管理器来完成。在VS中右键点击你的项目名称 - “管理NuGet程序包”。在浏览标签页中搜索“System.Data.SQLite”。这里你会看到多个相关包。正如原始资料提到的常见的有System.Data.SQLite.Core这是核心包只包含运行所需的最基本库文件本地互操作库和托管程序集。它不依赖其他NuGet包更干净。这是我们推荐的选择。System.Data.SQLite这是一个“捆绑包”它除了包含Core的内容还会根据你的项目框架x86/x64自动引入对应的本地库Native包。对于新手它可能更方便因为它帮你处理了平台依赖。我个人的实操心得是在绝大多数现代开发场景下直接安装System.Data.SQLite.Core即可。因为你的开发机通常是x64和部署目标也通常是x64架构明确Core包能正常工作。安装时在NuGet界面右侧确认版本选择稳定的发布版本如1.0.118进行安装。安装成功后你会在项目依赖项的“包”下面看到它。这个包为我们提供了关键的SQLiteConnection,SQLiteCommand,SQLiteDataAdapter等类。3. 界面设计与核心控件布局3.1 主窗体设计构建用户交互骨架现在让我们来设计程序的界面。双击解决方案资源管理器中的Form1.cs打开窗体设计器。一个空白的窗体会出现。首先调整一下窗体的基础属性让它在启动时更友好在属性窗口按F4可打开找到Text属性将其从“Form1”改为“SQLite数据库浏览器”。这是窗体的标题。建议将StartPosition属性设置为CenterScreen这样程序启动时会自动出现在屏幕中央。可以适当调整Size属性比如设置为800x600提供一个初始的合理大小。接下来从左侧的“工具箱”面板如果没看到可通过“视图”-“工具箱”打开拖放我们需要的控件到窗体上Button按钮拖放一个Button到窗体左上角。在属性窗口中将其(Name)改为btnOpenDb前缀btn是按钮的命名约定提高代码可读性Text属性改为“打开数据库”。DataGridView表格控件这是我们的主角。从工具箱拖放一个DataGridView到窗体上。调整其位置和大小使其占据窗体下方大部分区域。将其(Name)改为dataGridView1默认即可也可改为dgvData。在属性窗口中找到Anchor属性将其设置为Top, Bottom, Left, Right。这样当窗体大小变化时表格会自动随之缩放填满可用空间。StatusStrip状态栏可选但推荐拖放一个StatusStrip控件到窗体底部。它里面会自动包含一个ToolStripStatusLabel。将其(Name)改为statusLabelText属性清空。我们将用它来显示当前打开的数据库路径或操作状态。一个典型的基础布局就完成了顶部一个操作按钮中间是数据展示区域底部是状态提示区。设计器视图让你能直观地调整控件位置和大小远比手写坐标代码高效。3.2 控件事件关联从点击到执行WinForms采用事件驱动模型。我们需要为按钮的点击事件添加处理逻辑。在设计器中双击你刚刚放置的“打开数据库”按钮。VS会自动在Form1.cs代码文件中生成一个名为btnOpenDb_Click的事件处理方法并将焦点跳转到那里。这是连接用户界面操作点击与后台代码打开文件、查询数据的桥梁。所有核心的数据操作逻辑我们都将从这个方法开始编写。4. 核心功能实现连接、查询与展示4.1 实现文件选择与数据库连接在btnOpenDb_Click方法中我们首先要实现让用户选择一个本地的.db或.sqlite数据库文件。这里使用OpenFileDialog类。private void btnOpenDb_Click(object sender, EventArgs e) { // 1. 创建并配置打开文件对话框 using (OpenFileDialog openFileDialog new OpenFileDialog()) { openFileDialog.Filter SQLite数据库文件 (*.db;*.sqlite;*.sqlite3)|*.db;*.sqlite;*.sqlite3|所有文件 (*.*)|*.*; openFileDialog.FilterIndex 1; // 默认选择第一个过滤器 openFileDialog.RestoreDirectory true; // 记住上次打开的目录 // 2. 显示对话框并判断用户是否点击了“打开” if (openFileDialog.ShowDialog() DialogResult.OK) { string dbFilePath openFileDialog.FileName; // 在状态栏显示路径 statusLabel.Text $已打开: {dbFilePath}; // 3. 调用方法加载并显示数据下一步实现 LoadDataFromDatabase(dbFilePath); } else { statusLabel.Text 用户取消了操作。; } } }代码解析与注意事项Filter属性定义了对话框中可选的文件类型。这里我们指定了SQLite常见的几种文件扩展名。|符号是分隔符前面是显示给用户的描述后面是实际的文件扩展名匹配模式。提供“所有文件”选项是个好习惯。using语句确保OpenFileDialog在使用完毕后被正确释放资源这是一个重要的好习惯。ShowDialog()方法以模态方式显示对话框意味着它会阻塞直到用户关闭它。返回值DialogResult.OK表示用户点击了“打开”。获取到文件路径dbFilePath后下一步就是连接数据库。我们创建一个独立的方法LoadDataFromDatabase来保持代码清晰。4.2 使用SQLiteDataAdapter填充DataTableLoadDataFromDatabase方法是数据操作的核心。我们将在这里建立连接、执行查询并将结果绑定到表格。private void LoadDataFromDatabase(string dbFilePath) { // 定义连接字符串。Data Source指定数据库文件路径。 string connectionString $Data Source{dbFilePath};Version3;; // 假设我们想读取数据库中一个名为SensorData的表。 // 在实际应用中你可以让用户选择表或通过查询sqlite_master表获取所有表名。 string query SELECT * FROM SensorData; // 请根据你的实际表名修改 // 关键对象声明在using块外以便在finally块中处理清理 SQLiteConnection connection null; SQLiteDataAdapter dataAdapter null; DataTable dataTable new DataTable(); // 用于在内存中存储查询结果 try { // 1. 创建并打开数据库连接 connection new SQLiteConnection(connectionString); connection.Open(); // 2. 创建DataAdapter它充当数据库和内存DataTable之间的桥梁 dataAdapter new SQLiteDataAdapter(query, connection); // 3. 清空可能的旧数据然后用查询结果填充DataTable dataTable.Clear(); dataAdapter.Fill(dataTable); // 4. 将DataTable绑定到DataGridView dataGridView1.DataSource dataTable; // 更新状态栏显示记录数 statusLabel.Text ${dbFilePath} - 共 {dataTable.Rows.Count} 条记录; } catch (SQLiteException ex) { // 专门捕获SQLite相关的异常如文件损坏、SQL语法错误、表不存在等 MessageBox.Show($数据库操作错误:\n{ex.Message}, 错误, MessageBoxButtons.OK, MessageBoxIcon.Error); statusLabel.Text 数据加载失败。; } catch (Exception ex) { // 捕获其他通用异常 MessageBox.Show($发生未知错误:\n{ex.Message}, 错误, MessageBoxButtons.OK, MessageBoxIcon.Error); statusLabel.Text 发生错误。; } finally { // 5. 确保资源被释放这是至关重要的步骤 dataAdapter?.Dispose(); // 连接在using块中会自动关闭但这里显式检查一下也无妨 connection?.Close(); connection?.Dispose(); } }核心原理解析SQLiteConnection代表一个到SQLite数据库文件的唯一会话。Open()方法建立连接。务必在操作结束后Close()或Dispose()。SQLiteDataAdapter这是一个适配器它的核心作用是弥合数据库世界面向集合、SQL和.NET内存对象世界DataTable之间的差异。你给它一个SQL查询SELECT *和一个连接它的Fill()方法会帮你执行查询自动将结果集的架构列名、数据类型映射到DataTable并把所有数据行取回来塞进DataTable。它比直接用SQLiteCommand和SQLiteDataReader手动循环读取要方便得多尤其适合绑定到UI控件。DataTable一个内存中的关系数据表。它是DataGridView的完美数据源。DataGridView.DataSource dataTable;这一行代码就完成了数据绑定。DataGridView会自动根据DataTable的列创建表头并将每一行数据填充进去。重要避坑技巧务必使用try-catch-finally块包裹数据库操作。文件可能被占用、路径可能错误、SQL可能有语法问题、表可能不存在。良好的异常处理能防止程序崩溃并给用户明确的错误提示。在finally块中释放资源Dispose是必须的即使发生异常也要执行以防连接泄露。4.3 DataGridView的进阶配置与美化默认绑定数据后DataGridView可能看起来有些简陋。我们可以在窗体加载事件Form1_Load或数据绑定后对其进行一些优化配置。private void Form1_Load(object sender, EventArgs e) { // 配置DataGridView的一些常用属性提升用户体验 ConfigureDataGridView(); } private void ConfigureDataGridView() { // 允许用户调整列宽 dataGridView1.AllowUserToResizeColumns true; // 允许用户调整行高 dataGridView1.AllowUserToResizeRows false; // 通常行高固定更美观 // 设置默认行高 dataGridView1.RowTemplate.Height 25; // 启用交替行样式斑马线提高可读性 dataGridView1.AlternatingRowsDefaultCellStyle.BackColor Color.LightGray; // 设置选择模式为整行选择 dataGridView1.SelectionMode DataGridViewSelectionMode.FullRowSelect; // 允许多行选择 dataGridView1.MultiSelect true; // 设置边框样式 dataGridView1.BorderStyle BorderStyle.Fixed3D; // 自动根据内容调整列宽慎用数据量大时可能卡顿 // dataGridView1.AutoSizeColumnsMode DataGridViewAutoSizeColumnsMode.AllCells; // 更推荐的方式填充模式 dataGridView1.AutoSizeColumnsMode DataGridViewAutoSizeColumnsMode.Fill; }配置选择建议AutoSizeColumnsMode设置为Fill会让各列自动拉伸以填满表格宽度比较美观。但如果某些列内容特别长可能会被压缩。另一种策略是设为None然后手动设置某些关键列的宽度dataGridView1.Columns[列名].Width 100或者让用户手动调整。虚拟模式如果你的数据量非常大数万行以上一次性加载到DataTable会占用大量内存。这时可以考虑启用DataGridView的虚拟模式VirtualMode true并自己实现数据的分页加载。但对于大多数小型SQLite应用一次性加载是简单可行的。5. 功能扩展与实战技巧5.1 实现数据筛选与动态查询一个只会显示整张表的应用是简陋的。让我们添加一个文本框和一个按钮让用户可以输入条件进行筛选。界面添加在窗体设计器在“打开数据库”按钮旁边拖放一个TextBox控件命名为txtFilter和一个Button控件命名为btnFilterText属性为“筛选”。修改查询逻辑我们需要重构LoadDataFromDatabase方法使其能接受一个可选的WHERE条件。private void LoadDataFromDatabase(string dbFilePath, string whereClause ) { string connectionString $Data Source{dbFilePath};Version3;; // 动态构建查询语句 string baseQuery SELECT * FROM SensorData; string fullQuery string.IsNullOrWhiteSpace(whereClause) ? baseQuery : ${baseQuery} WHERE {whereClause}; // ... 其余代码创建连接、DataAdapter、Fill等与之前完全相同 ... // 只需将之前写死的 query 变量替换为这里的 fullQuery 即可。 dataAdapter new SQLiteDataAdapter(fullQuery, connection); // ... }实现筛选按钮事件双击“筛选”按钮生成btnFilter_Click事件。private void btnFilter_Click(object sender, EventArgs e) { if (string.IsNullOrWhiteSpace(_currentDbPath)) // _currentDbPath是一个类级变量在打开文件时保存 { MessageBox.Show(请先打开一个数据库文件。, 提示, MessageBoxButtons.OK, MessageBoxIcon.Information); return; } string filterText txtFilter.Text.Trim(); string whereClause ; if (!string.IsNullOrEmpty(filterText)) { // 假设我们想对“SensorName”列进行模糊查询 // 注意直接拼接字符串有SQL注入风险此处仅为演示简单场景。 // 对于生产环境应使用参数化查询下文会讲。 whereClause $SensorName LIKE %{filterText}%; } // 重新加载数据应用筛选条件 LoadDataFromDatabase(_currentDbPath, whereClause); }这里引入了一个类级变量private string _currentDbPath;在btnOpenDb_Click成功打开文件后需要将dbFilePath赋值给它以便在其他方法中使用。5.2 安全加固参数化查询防止SQL注入上面的筛选示例直接将用户输入拼接到SQL字符串中这是极其危险的会引发SQL注入攻击。正确的做法是使用参数化查询。修改LoadDataFromDatabase方法中创建DataAdapter的部分private void LoadDataFromDatabase(string dbFilePath, string filterColumn null, string filterValue null) { // ... 连接字符串等 ... string baseQuery SELECT * FROM SensorData; SQLiteCommand command new SQLiteCommand(baseQuery, connection); // 如果提供了筛选条件和值则修改查询并添加参数 if (!string.IsNullOrWhiteSpace(filterColumn) !string.IsNullOrWhiteSpace(filterValue)) { baseQuery $ WHERE {filterColumn} LIKE searchValue; command.CommandText baseQuery; // 添加参数值中的%通配符在这里处理 command.Parameters.AddWithValue(searchValue, $%{filterValue}%); } // 使用带命令的构造函数创建DataAdapter dataAdapter new SQLiteDataAdapter(command); // ... 后续Fill操作不变 ... }同时修改btnFilter_Clickprivate void btnFilter_Click(object sender, EventArgs e) { if (string.IsNullOrWhiteSpace(_currentDbPath)) { MessageBox.Show(请先打开一个数据库文件。, 提示, MessageBoxButtons.OK, MessageBoxIcon.Information); return; } string filterValue txtFilter.Text.Trim(); // 假设我们固定对SensorName列进行筛选 string filterColumn SensorName; LoadDataFromDatabase(_currentDbPath, filterColumn, filterValue); }关键点searchValue是一个命名参数。AddWithValue方法将用户输入的filterValue作为一个参数值安全地传递给SQL引擎而不是将其作为SQL语句的一部分进行拼接。SQLite引擎会确保这个值被当作纯数据处理即使它包含SQL关键字或引号也不会被解释为指令从而从根本上杜绝了注入。5.3 实现简单的数据修改与保存DataGridView默认允许用户编辑单元格。但编辑后的数据并不会自动保存回数据库。我们需要增加一个“保存”按钮并利用SQLiteDataAdapter的Update方法。界面添加在窗体上添加一个Button命名为btnSaveText属性为“保存更改”。理解Update机制SQLiteDataAdapter不仅可以Fill数据还可以通过关联的SQLiteCommandBuilder对象自动生成用于更新Update、插入Insert、删除Delete的SQL命令。当DataTable中的数据被修改后调用dataAdapter.Update(dataTable)这些自动生成的命令就会被执行将内存中的更改同步回数据库。实现保存逻辑private void btnSave_Click(object sender, EventArgs e) { if (dataGridView1.DataSource null) { MessageBox.Show(没有加载任何数据。, 提示, MessageBoxButtons.OK, MessageBoxIcon.Information); return; } DataTable dt (DataTable)dataGridView1.DataSource; if (!dt.HasChanges()) // 检查是否有未保存的更改 { MessageBox.Show(数据没有变化无需保存。, 提示, MessageBoxButtons.OK, MessageBoxIcon.Information); return; } string connectionString $Data Source{_currentDbPath};Version3;; using (SQLiteConnection conn new SQLiteConnection(connectionString)) { // 重新创建Adapter和CommandBuilder SQLiteDataAdapter adapter new SQLiteDataAdapter($SELECT * FROM {_currentTableName}, conn); // 需要知道表名 SQLiteCommandBuilder commandBuilder new SQLiteCommandBuilder(adapter); try { conn.Open(); // 关键的一行执行更新 int rowsAffected adapter.Update(dt); MessageBox.Show($成功保存 {rowsAffected} 处更改。, 成功, MessageBoxButtons.OK, MessageBoxIcon.Information); // 接受DataTable中的更改使HasChanges()变为false dt.AcceptChanges(); } catch (Exception ex) { MessageBox.Show($保存失败:\n{ex.Message}, 错误, MessageBoxButtons.OK, MessageBoxIcon.Error); // 如果保存失败拒绝DataTable中的更改回滚到原始状态 dt.RejectChanges(); } } }实操心得与陷阱表名与主键SQLiteCommandBuilder要能正确生成Update/Insert/Delete语句原始查询的SELECT语句必须包含单表的主键列。如果表没有定义主键或者你的查询没有包含主键CommandBuilder将无法工作。确保你的基础查询是类似SELECT * FROM TableName或明确包含了主键列。并发冲突这是一个简单的实现没有处理多用户同时修改的并发冲突。在正式应用中需要考虑使用时间戳或检查原值等乐观并发策略。AcceptChanges/RejectChanges调用Update成功后调用dt.AcceptChanges()会将DataTable中所有行的状态标记为“未更改”。如果保存失败调用dt.RejectChanges()会撤销所有未提交的修改让表格数据恢复到上一次AcceptChanges时的状态。这对于提供良好的用户体验很重要。6. 部署、调试与性能优化6.1 项目发布与部署开发完成后你需要将程序打包分发给用户。在VS中右键项目 - “发布”。对于WinForms .NET 8应用你有几种部署模式框架依赖生成的文件较小但要求目标机器上安装有对应版本的.NET运行时。用户需要先安装.NET 8 Desktop Runtime。独立将.NET运行时和你的应用一起打包生成一个较大的文件夹或单个可执行文件。优点是用户无需额外安装任何东西开箱即用。这是目前最推荐的部署方式尤其是对于内部工具或分发给不熟悉技术的用户。在发布配置向导中选择“独立”目标运行时选择“win-x64”如果你的用户是64位Windows。发布完成后会在输出目录生成一个包含你程序所有依赖的文件夹直接压缩这个文件夹发给用户即可运行。6.2 使用DB Browser for SQLite进行调试在开发过程中你手头可能没有现成的SQLite数据库文件或者需要验证程序读取的数据是否正确。DB Browser for SQLite (DB4S)是一个免费、开源、图形化的SQLite数据库管理工具强烈推荐安装。它的核心用途创建和修改数据库结构你可以轻松地新建.db文件创建表设计字段列名、数据类型、主键、约束等。可视化管理数据像在Excel中一样直接浏览、添加、编辑、删除表中的数据行。执行SQL查询有一个专门的标签页可以编写和运行任意SQL语句并查看结果这对于测试你的查询逻辑非常方便。导入/导出数据支持从CSV、SQL文件导入数据或将数据导出为多种格式。当你的程序读取数据出现异常时第一时间用DB4S打开同一个数据库文件检查表是否存在、数据结构是否匹配、数据内容是否正常可以快速定位问题是出在你的代码还是数据源本身。6.3 常见性能问题与优化策略当你的SQLite数据库文件增长到几十MB甚至上百MB数据行数达到十万、百万级别时一些操作可能会变慢。以下是一些实战中的优化思路查询优化避免SELECT *尤其是在表有很多列但你只需要其中几列时。明确指定需要的列名如SELECT Id, Name, Value FROM SensorData可以减少从磁盘读取和网络传输虽然这里是本地的数据量。为查询条件列建立索引这是提升查询速度最有效的手段。如果btnFilter_Click经常按SensorName或Timestamp进行筛选你应该在数据库中对这些列创建索引。CREATE INDEX idx_sensorname ON SensorData (SensorName); CREATE INDEX idx_timestamp ON SensorData (Timestamp);可以在DB4S中轻松执行这些SQL语句来创建索引。注意索引会略微增加插入和更新数据的时间并占用额外空间但对于以读为主的应用利远大于弊。分页加载这是处理海量数据展示的黄金法则。不要一次性用dataAdapter.Fill(dt)加载所有数据。修改查询使用LIMIT和OFFSET子句。int pageSize 100; // 每页显示100行 int currentPage 0; // 当前页码 string query $SELECT * FROM SensorData ORDER BY Id LIMIT {pageSize} OFFSET {currentPage * pageSize};然后在界面上提供“上一页”、“下一页”按钮来更新currentPage并重新加载数据。DataGridView只绑定当前页的数据内存占用和渲染速度都会得到极大改善。异步操作如果你的查询确实很复杂耗时较长比如多表关联聚合在Fill数据时界面会“卡死”。这时可以使用异步方法。private async Task LoadDataAsync(string dbFilePath) { // ... 创建连接、命令等 ... DataTable dt new DataTable(); await Task.Run(() { // 在后台线程执行耗时的Fill操作 using (var adapter new SQLiteDataAdapter(command)) { adapter.Fill(dt); } }); // 回到UI线程更新控件 dataGridView1.Invoke((MethodInvoker)delegate { dataGridView1.DataSource dt; }); }并在按钮点击事件中调用await LoadDataAsync(...);。注意窗体类的声明需要加上async关键字。7. 故障排除与经验实录即使按照步骤操作你也可能会遇到一些问题。这里记录了几个我踩过的坑和解决方案问题1运行时报错“找不到SQLite.Interop.dll”或“无法加载DLL‘SQLite.Interop.dll’”原因System.Data.SQLite.Core包依赖于本机互操作库Native DLL。在开发时VS能正确引用但如果你直接复制bin\Debug下的exe文件到别处运行可能会缺失这些依赖。解决方案确保发布或拷贝时将bin\Debug或bin\Release目录下的整个文件夹一起拷贝不要只拷贝exe文件。里面应该包含x86和x64子文件夹其中就有SQLite.Interop.dll。或者在项目属性 - 生成 - 平台目标中指定具体的平台如x64而不是“Any CPU”。然后发布为“独立”部署.NET运行时会帮你打包正确的本地库。问题2DataGridView显示“(null)”或列名是奇怪的名称原因DataGridView的列标题默认绑定的是DataTable的ColumnName。如果你的SQL查询使用了别名SELECT id AS 编号或者列名本身包含特殊字符显示可能不如预期。解决方案在数据绑定后可以遍历DataGridView的列自定义其标题文本。dataGridView1.DataSource dataTable; if (dataGridView1.Columns.Contains(InternalColumnName)) { dataGridView1.Columns[InternalColumnName].HeaderText 显示给用户的列名; } // 或者更通用地根据DataTable的列信息来设置 // for (int i 0; i dataTable.Columns.Count; i) // { // dataGridView1.Columns[i].HeaderText dataTable.Columns[i].ColumnName; // }问题3保存数据时SQLiteCommandBuilder没有生成正确的更新语句导致报错原因与排查表无主键这是最常见的原因。用DB4S打开数据库检查你的表是否设置了主键PRIMARY KEY。SQLiteCommandBuilder需要主键来唯一标识一行以生成WHERE子句。查询不包含主键即使表有主键如果你的初始查询创建DataAdapter时用的SELECT语句没有包含主键列比如用了SELECT name, value FROM table而主键是idCommandBuilder也无法工作。确保SELECT语句包含所有列SELECT *或至少包含主键列。表名或列名有空格或特殊字符如果表名是[My Table]或列名是[Column A]自动生成的SQL可能会出错。尽量使用下划线代替空格如My_Table。问题4打开数据库文件时提示“数据库文件被锁定”原因SQLite数据库文件在同一时间只允许一个写入者。可能的原因有你的程序之前打开连接后没有正确关闭另一个程序如DB4S正以写入模式打开该文件或者文件位于网络共享上存在权限或延迟问题。解决方案检查代码确保所有SQLiteConnection对象都在using块中或是在finally块中显式调用了Close()和Dispose()。关闭可能正在访问该文件的其他程序如DB4S。如果问题在网络路径上考虑将数据库文件复制到本地临时目录进行操作。掌握了从环境搭建、界面设计、核心数据绑定到功能扩展、安全加固、性能优化和故障排查的这一整套流程你已经具备了使用C# WinForms和SQLite开发稳健、实用桌面数据库应用的基本能力。这套技术组合的魅力在于其简单直接和高效能够让你快速将想法转化为可用的工具。接下来你可以尝试为你的应用添加更多的功能比如图表展示结合ZedGraph或LiveCharts、数据导出为Excel或PDF、多标签页同时浏览多个表甚至是基于LINQ的更优雅的数据查询方式。