资讯详情

C# 操作 Access 数据库实战:从建库建表到批量写入的完整指南

📅 2026/10/12 21:38:53 | 华诺云谱 👁 阅读
C# 操作 Access 数据库实战:从建库建表到批量写入的完整指南
简介这是一套面向C#开发者、数据库初学者及需要快速集成Access能力的工程师的源码工程围绕Access数据库的创建与操作封装了独立类库可直接引入任意项目复用省去从零搭建的时间。资源包共97个文件以41个dll、13个cs源码、8个json配置及若干pdb、exe、csproj工程文件为主压缩包约2.53MB结构清晰便于按模块查阅。工程覆盖三种常用连接字符串获取、代码创建数据库与数据表、通用SQL执行帮助类含单条与批量插入、事务处理、指定内容查询、分页查询、更新、删除以及获取库中所有表名与字段名等能力并附带完整的增删查改使用示例。目前已有1997人学习下载适合希望把精力集中在业务逻辑、快速掌握C#操作Access完整链路的读者参考借鉴。1. 从一次上位机改造说起为什么 C# 操作 Access 至今没被淘汰去年帮一家做扭矩枪数据采集的客户改上位机现场十几台工位机跑的都是 C# WinForm本地存的是.accdb文件每天几千条拧紧记录还要按班次导出报表。客户问我要不要换成 SQL Server我算了一笔账单机数据量不到 50 万行没有并发写入没有远程访问换数据库意味着重写连接层、重做安装包、还要给现场装服务——收益几乎为零。这就是 Access 在 C# 上位机、考勤机、小型教务系统里长期存在的真实原因它是文件型数据库零部署一个.accdb拷走就是全部数据。这个标题讲的是用 C# 从零建库、建表、增删改查、批量导入、参数化防注入的一整套源码工程结构。适合正在做单机版管理软件、设备数据采集、报表工具的人也适合刚学完 C# 基础语法、想找一个能跑通的完整数据库项目练手的人。下面我按实际工程顺序拆先讲清 Access 在 C# 里的技术底座再给可复制的建库建表代码然后是查询、批量写入和工程分层最后是避坑和进阶技巧。2. Access 在 C# 里的技术底座OLEDB、ACE 引擎与连接字符串2.1 为什么是 OleDb 而不是 SqlClientAccess 不是 SQL Server它没有独立的服务进程C# 访问它走的是System.Data.OleDb命名空间底层是微软的 ACE 引擎Access Connectivity Engine。很多人第一次写会习惯性using System.Data.SqlClient然后发现根本没有对应的连接类这是最常见的入门翻车点。ACE 引擎分 32 位和 64 位两个版本必须和你的 C# 程序目标平台一致。这就是热搜里那句「请先安装 Access 数据库 64 位系统驱动程序」的由来——如果你的项目编译成 x64但机器上只装了 32 位驱动运行时会直接抛未在本地计算机上注册 Microsoft.ACE.OLEDB.12.0 提供程序。这个报错我见过太多次九成不是代码问题是驱动位数不匹配。连接字符串的两种典型写法// 方式一连接已有的 .accdb 文件Access 2007 及以上 string connStr ProviderMicrosoft.ACE.OLEDB.12.0;Data SourceD:\Data\workshop.accdb;Persist Security InfoFalse;; // 方式二带数据库密码的写法 string connStrPwd ProviderMicrosoft.ACE.OLEDB.12.0;Data SourceD:\Data\workshop.accdb;Jet OLEDB:Database Passwordyourpwd;;Provider固定用Microsoft.ACE.OLEDB.12.0老式的Microsoft.Jet.OLEDB.4.0只支持.mdb且只有 32 位新项目不要再用。Data Source建议用绝对路径相对路径在 WinForm 里会跟着工作目录变调试时正常、发布后找不到库这种玄学问题排查起来很费时间。2.2 目标平台与驱动的对应关系程序目标平台需要的 ACE 驱动典型报错x8632 位 ACE注册表找不到提供程序x6464 位 ACE未注册 Microsoft.ACE.OLEDB.12.0AnyCPU跟随系统风险最高在 64 位系统上默认按 64 位跑我一般会把项目目标平台明确设成 x64然后在部署说明里写清楚装 64 位驱动避免 AnyCPU 带来的不确定性。如果你的程序还要调用 32 位的第三方 DLL比如某些老旧的相机 SDK那就只能锁 x86这时候驱动也必须装 32 位二者不能混。2.3 建库建表用 SQL 还是用 ADOXAccess 的.accdb文件本身可以用代码创建不一定非要先手动建好。常见做法是用 ADOXADO Extensions for DDL and Security来建库再用 SQL 建表using ADOX; // 需引用 COM 组件 Microsoft ADO Ext. for DDL and Security public static void CreateDatabase(string filePath) { if (File.Exists(filePath)) return; var catalog new CatalogClass(); // 创建空的 accdb 文件 catalog.Create($ProviderMicrosoft.ACE.OLEDB.12.0;Data Source{filePath};); catalog null; } public static void CreateTables(string connStr) { string sql CREATE TABLE TorqueRecord ( Id AUTOINCREMENT PRIMARY KEY, StationNo TEXT(20), TorqueValue DOUBLE, ResultFlag TEXT(4), CollectTime DATETIME); using (var conn new OleDbConnection(connStr)) using (var cmd new OleDbCommand(sql, conn)) { conn.Open(); cmd.ExecuteNonQuery(); } }AUTOINCREMENT是 Access 的自增关键字不是 SQL Server 的IDENTITY写错会直接语法报错。TEXT(20)对应短文本DOUBLE对应双精度DATETIME对应日期时间。Access 的字段类型比 SQL Server 少没有VARCHAR(MAX)长文本要用MEMO。建表语句里不要用NVARCHARAccess 不认。提示ADOX 是 COM 组件需要在项目引用里添加「Microsoft ADO Ext. for DDL and Security」并且同样受位数限制。如果不想引入 COM 依赖也可以让程序首次运行时检测文件不存在就提示用户手动建一个空库工程上更省事。3. 增删改查与参数化把注入和类型错误挡在门外3.1 参数化查询的标准写法Access 的 OleDb 参数是位置参数不是命名参数name只是占位符顺序必须和 SQL 里出现的顺序一致。这一点和 SqlClient 不同很多人按 SqlClient 的习惯写结果参数错位查出来的数据对不上。public static ListTorqueRecord QueryByStation(string connStr, string stationNo, DateTime from) { var list new ListTorqueRecord(); string sql SELECT Id, StationNo, TorqueValue, ResultFlag, CollectTime FROM TorqueRecord WHERE StationNo ? AND CollectTime ? ORDER BY CollectTime DESC; using (var conn new OleDbConnection(connStr)) using (var cmd new OleDbCommand(sql, conn)) { // 参数顺序必须与 SQL 中 ? 的出现顺序严格一致 cmd.Parameters.AddWithValue(?, stationNo); cmd.Parameters.AddWithValue(?, from); conn.Open(); using (var reader cmd.ExecuteReader()) { while (reader.Read()) { list.Add(new TorqueRecord { Id Convert.ToInt32(reader[Id]), StationNo reader[StationNo].ToString(), TorqueValue Convert.ToDouble(reader[TorqueValue]), ResultFlag reader[ResultFlag].ToString(), CollectTime Convert.ToDateTime(reader[CollectTime]) }); } } } return list; }AddWithValue的第二个参数是值OleDb 会根据值的类型推断字段类型。这里有个坑如果传DateTime但数据库字段是文本或者传字符串去比日期字段Access 不会像 SQL Server 那样自动隐式转换会直接报「标准表达式中数据类型不匹配」。稳妥做法是显式指定OleDbTypecmd.Parameters.Add(?, OleDbType.VarWChar).Value stationNo; cmd.Parameters.Add(?, OleDbType.Date).Value from;3.2 日期字面量的写法差异Access 的 SQL 里日期字面量必须用#包裹不是单引号-- 正确 SELECT * FROM TorqueRecord WHERE CollectTime #2024-01-01# -- 错误Access 会当成字符串比较 SELECT * FROM TorqueRecord WHERE CollectTime 2024-01-01用参数化就没这个问题但如果你在调试时手写 SQL 测试这个差异一定要记住。我见过有人把日期写成字符串查询不报错但结果永远为空查了半天以为是数据没写进去。3.3 更新和删除的注意事项public static int UpdateResult(string connStr, int id, string flag) { string sql UPDATE TorqueRecord SET ResultFlag ? WHERE Id ?; using (var conn new OleDbConnection(connStr)) using (var cmd new OleDbCommand(sql, conn)) { cmd.Parameters.Add(?, OleDbType.VarWChar).Value flag; cmd.Parameters.Add(?, OleDbType.Integer).Value id; conn.Open(); return cmd.ExecuteNonQuery(); // 返回受影响行数 } }ExecuteNonQuery返回受影响行数更新时如果返回 0说明WHERE条件没匹配到记录不要想当然认为执行成功了。删除同理。Access 不支持TRUNCATE TABLE清空表要用DELETE FROM 表名数据量大时速度会慢这是文件型数据库的固有特性。4. 批量写入与工程分层让几千条数据不卡界面4.1 为什么逐条 Insert 会卡Access 每次ExecuteNonQuery都是一次独立的事务提交写磁盘。几百条还能忍几千条就会让 WinForm 界面假死。热搜里提到的SqlBulkCopy是 SQL Server 专属Access 没有对应的高效批量接口只能靠事务把多条 Insert 包起来减少提交次数。public static void BatchInsert(string connStr, ListTorqueRecord records) { using (var conn new OleDbConnection(connStr)) { conn.Open(); using (var tran conn.BeginTransaction()) // 开启事务 using (var cmd new OleDbCommand( INSERT INTO TorqueRecord (StationNo, TorqueValue, ResultFlag, CollectTime) VALUES (?,?,?,?), conn, tran)) { // 预声明参数循环里只改值避免重复创建命令对象 cmd.Parameters.Add(?, OleDbType.VarWChar); cmd.Parameters.Add(?, OleDbType.Double); cmd.Parameters.Add(?, OleDbType.VarWChar); cmd.Parameters.Add(?, OleDbType.Date); foreach (var r in records) { cmd.Parameters[0].Value r.StationNo; cmd.Parameters[1].Value r.TorqueValue; cmd.Parameters[2].Value r.ResultFlag; cmd.Parameters[3].Value r.CollectTime; cmd.ExecuteNonQuery(); } tran.Commit(); // 一次性提交 } } }关键点是参数只Add一次循环里改Value命令对象复用。实测 5000 条记录逐条提交要十几秒包在一个事务里能压到 1 秒以内。注意事务不要开得太大几万条以上建议分批每 2000 条提交一次避免内存和日志膨胀。4.2 工程分层的推荐结构一个能长期维护的 Access 项目不要把所有 SQL 写在 Form 的按钮事件里。我一般分三层Models实体类比如TorqueRecord字段和表列一一对应。DAL数据访问层封装连接字符串、增删改查方法对外只暴露实体对象。UIWinForm 窗体只负责收集输入和展示结果不出现任何 SQL 字符串。连接字符串统一放在App.config的connectionStrings节点用ConfigurationManager.ConnectionStrings[WorkshopDb].ConnectionString读取。这样换数据库路径只改配置文件不用重新编译。// App.config // connectionStrings // add nameWorkshopDb // connectionStringProviderMicrosoft.ACE.OLEDB.12.0;Data Source|DataDirectory|\workshop.accdb; // providerNameSystem.Data.OleDb/ // /connectionStrings|DataDirectory|是 .NET 的占位符指向程序运行目录发布后不用改路径。但要注意它在某些宿主环境比如服务里解析结果可能和预期不同单机 WinForm 一般没问题。4.3 界面不卡的线程处理批量写入放在 UI 线程里即使有事务优化数据量再大也会卡。正确做法是丢到后台线程用Task.Run包起来写完再回 UI 线程更新进度条private async void btnImport_Click(object sender, EventArgs e) { var records LoadFromCsv(); // 假设从文件读入 progressBar.Maximum records.Count; await Task.Run(() { // 分批写入每批回调一次进度 for (int i 0; i records.Count; i 500) { var batch records.Skip(i).Take(500).ToList(); BatchInsert(connStr, batch); int done Math.Min(i 500, records.Count); // 跨线程更新控件必须用 Invoke this.Invoke(new Action(() progressBar.Value done)); } }); MessageBox.Show(导入完成); }Task.Run里的代码跑在线程池线程直接访问progressBar会抛跨线程异常必须Invoke回 UI 线程。这是 WinForm 上位机开发的基本功热搜里「c# timer 访问控件」「c# winform 如何更新状态栏与进度条」问的都是同一类问题。5. 避坑与排查Access 项目里最容易翻车的五件事5.1 报「未注册 Microsoft.ACE.OLEDB.12.0 提供程序」现象程序在本机调试正常拷到现场机器一运行就抛异常提示未注册提供程序。原因现场机器没装 ACE 驱动或者装的位数和程序目标平台不一致。64 位系统默认可能只带了 32 位驱动而你的程序编译成了 x64。解决确认程序目标平台安装对应位数的 ACE 驱动。装完后在「控制面板 → 管理工具 → 数据源 ODBC」里能看到Microsoft Access Driver (*.mdb, *.accdb)才算成功。如果同时需要 32 位和 64 位两个版本可以共存但要注意安装顺序先装 32 位再装 64 位有时会覆盖注册表项。5.2 查询结果为空但不报错现象SQL 语句看起来没问题ExecuteReader也不抛异常就是Read()一直返回 false。原因多半是日期字段用了单引号字符串比较或者参数顺序和?位置对不上导致条件永远不成立。解决先用参数化重写确认OleDbType和字段类型匹配。调试时可以把 SQL 和参数值打印出来手动在 Access 里跑一遍对比。日期字段一律用#或参数不要用单引号。5.3 数据库文件被占用无法删除或覆盖现象程序关闭后想替换.accdb文件提示文件被其他程序占用。原因OleDbConnection没有正确释放或者OleDbDataReader没关闭连接池还持有文件句柄。解决所有连接、命令、读取器都用using包起来确保Dispose被调用。如果确实需要强制释放可以在连接字符串里加OLE DB Services-4关闭连接池但会牺牲性能只建议在需要频繁替换文件的场景用。热搜里「c# 强行关闭被其他程序占用的文件」也是这个思路先找到持有句柄的进程再决定是等它释放还是强制结束。5.4 大批量写入中途失败数据只进了一半现象导入 5000 条跑到 3000 条时报错前面 3000 条已经写进去了后面 2000 条丢失数据处于不一致状态。原因没有用事务每条 Insert 独立提交中途异常就断了。解决用BeginTransaction把一批写入包起来全部成功才Commit任何一条失败就Rollback。如果数据量太大不适合单事务就分批每批一个事务记录已成功的批次号失败后从断点续传。5.5 字段名用了 Access 保留字现象建表或查询时报语法错误但 SQL 看起来完全正常。原因字段名用了Date、Time、Password、Level这类 Access 保留字。解决字段名一律加方括号比如[Date]、[Password]或者干脆改名成CollectDate、UserPwd。建表时就避开保留字比事后改表省事得多。我一般会在设计阶段就把字段名过一遍 Access 保留字列表这是血泪经验。6. 进阶技巧把 Access 工程做成能交付的产品6.1 用配置文件管理连接字符串和路径前面提过App.config这里补一个实际交付时的做法把数据库路径做成可配置项首次运行时如果检测到配置的路径不存在就弹窗让用户选择或自动在程序目录创建。这样现场部署不用改代码也不用重新编译。// 读取配置若为空则用默认路径 string dbPath ConfigurationManager.AppSettings[DbPath]; if (string.IsNullOrEmpty(dbPath)) { dbPath Path.Combine(AppDomain.CurrentDomain.BaseDirectory, workshop.accdb); } string connStr $ProviderMicrosoft.ACE.OLEDB.12.0;Data Source{dbPath};;AppDomain.CurrentDomain.BaseDirectory比Application.StartupPath更通用在非 WinForm 宿主里也能用。6.2 数据导出与备份Access 文件本身就是备份直接拷贝.accdb即可。但程序运行中文件被占用拷贝会失败。稳妥做法是先关闭所有连接再拷贝或者用File.Copy前先调用OleDbConnection.ClearAllPools()释放连接池。导出报表常用两种方式一是查出来用StreamWriter写 CSV简单可靠二是用Microsoft.Office.Interop.Excel写 xlsx但依赖 Office 安装现场机器不一定有。我一般优先 CSV需要格式再让用户自己用 Excel 打开。6.3 防止反编译与代码保护热搜里「c# 怎样防止反编译」在 Access 项目里同样适用。C# 编译出来的是 IL用工具能反编译回接近源码。常见做法是用Costura.Fody把 DLL 合并进主程序再用混淆工具处理关键逻辑。但说实话单机版管理软件的保护收益有限与其花大力气防反编译不如把数据校验和业务逻辑放在服务端——如果这个项目将来要联网的话。6.4 什么时候该换掉 AccessAccess 不是万能的。出现下面几种情况就该考虑迁移到 SQL Server 或 SQLite需要多台机器同时写入同一个库Access 的并发写入能力很弱容易锁表。单表数据量超过百万行查询明显变慢。需要远程访问Access 是文件型的不适合走网络共享。需要更严格的权限控制和审计。迁移时DAL 层的接口不用大改把OleDbConnection换成SqlConnectionSQL 方言调整一下AUTOINCREMENT改IDENTITY?参数改p大部分查询逻辑能复用。这也是我坚持分层的原因——换数据库时只动 DALUI 和 Models 基本不动。我自己的习惯是新项目先问三个问题——几台机器写、数据量多大、要不要联网。三个答案都是「单机、小量、不用」那就放心用 Access把精力花在业务逻辑和界面上别为了技术而技术。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑