比较两个DataTable数据(结构相同)——5万条数据仅需几秒

本文介绍了一种在无主键情况下对比两个DataTable的方法,用于实时更新可售商品记录。通过对比字段内容,实现新增和删除商品记录的功能。
似乎不是很正确........
 最近项目上有一个功能,要求是实时采集和更新可售商品记录。可售商品信息来源于另外一套销售系统的接口。
通常可售商品记录在8万条左右,采集的数据不提供主键,按照一系列的字段内容识别是否相同,好多字段为null值。(所以无法进行主键比较)
采集和保存的业务规则是:
从采集的数据中对照数据库中,采集的数据中存在而库中不存在的商品,属于新增加的商品记录,需要增加到库中。
而库中存在但采集的数据中不存在的,则属于已经销售的商品,需要从库中删除。
为此查阅了很多xdjm的代码,给出了若干解决方法。
可惜在数据量很小的情况还行,当使用实际数据进行时,对照时间已经不能忍受。
于是只能苦思冥想,自行写出了一个方法,贴出来供大家研究,如果发现有bug,请及时告知。
<think>嗯,用户这次的问题是如何用C#实现DataGridView和SQLite数据库的双向数据交互。结合之前提供的三个引用,虽然都不是直接针对SQLite的,但其中提到了数据库连接和Excel操作的技术点,可以借鉴部分思路。用户显然是个C#开发者,正在处理桌面应用的数据持久化求。从提问方式看,ta已经有一定C#基础,知道DataGridView组件,但对SQLite的具体操作不熟悉。真正要的不是零散的代码片段,而是一个完整的可运行解决方案——特别是SQLite这种轻量级数据库在本地应用中的典型用法。注意到引用[2]提到了SQLCipher库,这说明用户可能接触过加密数据库求,虽然当前问题没明确要求加密,但后续可能会涉及。准备在回答里提前埋个拓展说明会比较周到。技术上要解决几个关键点:1.SQLite连接字符串的写法(和之前的Access连接不同)2.DataGridView与DataTable的绑定机制3.SQLite的参数化查询(防注入很重要)4.事务处理(导入时批量操作要)示例代码要突出实用性:导出时考虑整表导出和条件导出两种场景;导入则要处理数据冲突的常见情况。特别要注意的是,SQLite不要像Access那样单独安装驱动,直接用System.Data.SQLite包就行。用户可能没意识到但实际会遇到的两个坑:-SQLite的日期格式处理-空值在DataGridView中的转换这些在注释里都得重点标注。最后预留的拓展方向是有意的:因为这种基础功能完善后,用户接下来必然会问性能优化或加密,提前准备接续点能减少后续沟通成本。</think> 好的,这里提供一个完整的C#示例,展示如何将`DataGridView`(简称`DGV`)数据**导出**到SQLite数据库以及从SQLite数据库**导入**到`DataGridView`中。 **核心思路:** 1. **连接SQLite数据库:** 使用合适的ADO.NET Provider (`System.Data.SQLite`库)。 2. **导出 (DGV -> SQLite):** * 遍历`DGV`的每一行。 * 为每一行构造一个参数化的SQL `INSERT`语句。 * 执行`INSERT`语句,将数据写入SQLite数据库表。 3. **导入 (SQLite -> DGV):** * 使用SQL `SELECT`语句从SQLite数据库表中查询数据。 * 将查询结果填充到`DataTable`。 * 将`DataTable`绑定到`DataGridView`的数据源。 **步骤与代码示例:** 1. **添加必要的引用 (NuGet Package):** 在Visual Studio中,通过NuGet包管理器安装 `System.Data.SQLite` 包。这是访问SQLite数据库的核心库。 ``` Install-Package System.Data.SQLite ``` 2. **准备Form:** * 在Form上放置一个`DataGridView`控件 (`dataGridView1`)。 * 放置两个按钮:`btnExport`(导出到SQLite)、`btnImport`(从SQLite导入)。 * (可选)放置一个`TextBox`或`SaveFileDialog`让用户选择或输入数据库文件路径。 3. **定义SQLite连接字符串 (替换路径):** 要根据你的实际路径修改`dbFilePath`。 ```csharp // 假设数据库文件名为 'mydatabase.db',放在应用程序启动目录下的 'Data' 文件夹中 (可自行修改) string dbFilePath = Path.Combine(Application.StartupPath, @"Data\mydatabase.db"); string connectionString = $"Data Source={dbFilePath};Version=3;"; ``` 4. **导出到SQLite代码 (`btnExport` Click 事件):** ```csharp private void btnExport_Click(object sender, EventArgs e) { try { // 创建连接 using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); // 开始一个事务,提高批量插入性能 using (SQLiteTransaction transaction = connection.BeginTransaction()) { // 假设目标表叫 'MyDataTable' (根据你的实际表名修改) string sqlInsert = "INSERT INTO MyDataTable (Column1, Column2, ...) VALUES (@value1, @value2, ...);"; // 循环遍历DGV每一行 foreach (DataGridViewRow dgvRow in dataGridView1.Rows) { // 检查是否是新建行(最后一行) if (!dgvRow.IsNewRow) { using (SQLiteCommand command = new SQLiteCommand(sqlInsert, connection, transaction)) { // 为参数赋值 // 假设你的表有两列: 'Name'(nvarchar) 和 'Age'(integer) // 修改@参数名、列名和赋值逻辑以匹配你的表结构和DGV列! command.Parameters.AddWithValue("@value1", dgvRow.Cells["ColumnNameName"].Value?.ToString() ?? DBNull.Value); // 处理null command.Parameters.AddWithValue("@value2", Convert.ToInt32(dgvRow.Cells["ColumnNameAge"].Value ?? 0)); // 处理null和类型转换 //... 继续为表中其他列添加参数 ... // 执行INSERT command.ExecuteNonQuery(); } } } // 提交事务 transaction.Commit(); } } MessageBox.Show("数据成功导出到SQLite数据库!"); } catch (Exception ex) { MessageBox.Show("导出失败: " + ex.Message); } } ``` **关键点说明:** * **表名 (`MyDataTable`) 和 列名 (`Column1`, `Column2`等):** **必须**替换为你的SQLite数据库中实际的表名和列名。 * **参数赋值 (`@value1`, `@value2`):** SQL语句中的参数名 (`@value1`, `@value2`) 必须与 `command.Parameters.AddWithValue` 中使用的参数名**完全一致**。参数值来源于对应的`DataGridView`单元格 (`dgvRow.Cells["YourColumnName"].Value`)。 * **Null 值处理:** 使用 `?.` 安全访问和 `?? DBNull.Value` 将C# `null` 转换为数据库能理解的 `DBNull`。 * **数据类型转换:** 确保DGV单元格值的类型能够转换为目标SQLite列的类型。`Convert.ToInt32`, `Convert.ToDouble` 等在此处做转换。 * **`IsNewRow`: 排除空行。** * **事务 (`Transaction`):** 在循环外部开始事务,循环内部插入数据,最后提交事务,极大地提升了批量数据插入的效率。 5. **从SQLite导入数据代码 (`btnImport` Click 事件):** ```csharp private void btnImport_Click(object sender, EventArgs e) { try { DataTable dt = new DataTable(); // 创建连接和命令 using (SQLiteConnection connection = new SQLiteConnection(connectionString)) { connection.Open(); // 查询数据 (替换表名和列名为你的实际情况) string sqlSelect = "SELECT Column1, Column2, ... FROM MyDataTable;"; using (SQLiteCommand command = new SQLiteCommand(sqlSelect, connection)) { // 使用DataAdapter填充DataTable using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(command)) { adapter.Fill(dt); } } } // 将DataTable绑定到DataGridView dataGridView1.DataSource = dt; MessageBox.Show("数据成功从SQLite数据库导入!"); } catch (Exception ex) { MessageBox.Show("导入失败: " + ex.Message); } } ``` **关键点说明:** * **SQL语句 (`sqlSelect`):** 这是查询数据的SQL语句,`SELECT * FROM MyDataTable` 会获取所有列的所有数据。你可以根据求修改,例如添加 `WHERE` 条件过滤。**替换 `MyDataTable` 为你的表名**。 * **`SQLiteDataAdapter`: 负责执行SQL命令并用结果填充 `DataTable`(`dt`)**。 * **`dataGridView1.DataSource = dt`:** 这是将查询到的数据(`dt`)显示在`DataGridView`上的核心绑定语句。`DGV`会自动根据`DataTable`的结构创建列并填充数据。 **重要提示:** 1. **列映射 (最关键):** 确保`DataGridView`的列名 (你在导出时 `dgvRow.Cells["YourColumnName"]` 中用的名字) 与你代码中**实际使用的 `DataGridView` 列的 `Name` 属性一致**!通常在窗体设计器中设置列的`Name`属性或者通过代码添加列时设置。同样,`INSERT`语句中的列名 (`Column1`, `Column2`) 必须和SQLite数据库表的实际列名一致。`SELECT`语句也要正确映射回来。 2. **数据库文件路径:** 确保代码中 `dbFilePath` 指定的路径存在且应用程序有读写权限。 3. **表结构:** 在运行导出代码之前,请确保SQLite数据库中已经存在名为 `MyDataTable`(或你修改后的名字)的表,并且表的列定义(列名、数据类型)能够兼容你要从`DGV`导出的数据。如果表不存在,你要先执行`CREATE TABLE`语句创建它。这可以在安装程序/第一次运行时完成,也可以用代码在执行`INSERT`前检查表是否存在并创建。 4. **错误处理:** 示例中提供了基本错误处理(`try...catch`),生产环境可能要更详细的错误日志记录5. **性能:** 对于大量数据,示例中的导出方式(逐行插入)在事务包裹下性能尚可。对于极其大量的数据 (>10万行),可能要考虑批量插入优化策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值