简介面向VB.Net初学者与需要快速上手Access桌面数据库开发的程序员这份资源通过完整示例演示了ADO.Net框架下的连接、查询、增删改与数据绑定流程。包内共18个文件包括vb源码、vbproj工程文件、sln解决方案、mdb数据库文件以及编译生成的exe、xml注释文档、pdb调试文件等压缩包仅47KB结构紧凑适合直接打开工程对照学习。内容涵盖Jet与ACE两种OLEDB提供程序的连接串写法、OleDbCommand参数化操作、DataAdapter与DataSet实现内存数据绑定等关键知识点既可作为课堂实训参考也可在开发中快速检索复用。已有229人下载学习可作为Access数据库操作入门的轻量级范例。1. 还在用 VB.Net 折腾 Access先把这层窗户纸捅破Access 数据库在不少人眼里是玩具但对做桌面工具、仓管系统、内部管理系统的人来说它依然是文件型数据库里最务实的选择之一。不需要装独立数据库服务一个 .accdb 文件拷走就能跑配合 VB.Net 的 OleDb 接口从连接、查询到界面数据绑定一套流程走完通常只需一天。这套示例包的价值不在于能不能连上而在于把连接串怎么写、参数怎么传、事务怎么控、并发怎么躲这些真实项目里反复踩的坑一次性讲透。适合理清楚思路再动手的开发者也适合接盘老系统时按图索骥查问题的维护者。2. OLEDB 连接与驱动选型32/64 位混战和连接串里的三个坑2.1 驱动前缀决定了你另一半的调试时间VB.Net 连 Access 走的是 OleDb连接串里最关键的是 Provider 那一项。老项目里最常见的是Microsoft.Jet.OLEDB.4.0它只能打开 .mdb 文件新一点的 .accdb 文件必须用 ACE 驱动也就是Microsoft.ACE.OLEDB.12.0或Microsoft.ACE.OLEDB.16.0。如果你的开发机装了 Office系统里大概率已有 ACE 驱动但发布到目标机器时经常没有这是部署环节最容易翻车的点。 标准 ACCDB 连接串 Dim connStr As String ProviderMicrosoft.ACE.OLEDB.12.0;Data SourceC:\Data\demo.accdb;Persist Security InfoFalse; Dim conn As New OleDbConnection(connStr) conn.Open()逻辑说明Provider告诉 OleDb 运行时加载哪个驱动Data Source是数据库文件的绝对路径Persist Security InfoFalse表示连接成功后不保留密码等敏感信息。注意 Access 数据库密码有两种——打开密码和用户级密码前者放在连接串的Jet OLEDB:Database Password参数里后者已经很少用了。参数建议如果目标机器只装过旧版 OfficeACE 12.0 驱动缺失时你会在conn.Open()那行收到未找到提供程序的异常。常见做法是随程序附一份 Access Database Engine 可再发行组件安装包安装时按目标平台选 x86 还是 x64这个选择直接影响下一节的内容。2.2 平台位数不一致时连报错都会骗你ACE 驱动有一个很隐蔽的特性同一个安装包x86 版只能被 32 位进程调用x64 版只能被 64 位进程调用两边互不兼容。如果你的程序以 x86 编译而开发机上装的是 x64 驱动代码里的conn.Open()会抛异常提示信息往往含糊其辞只说未指定错误或检索 COM 类工厂时 CLSID 失败不熟悉的人很容易在业务代码里找半天原因。 强制在 32 位模式下编译VB.Net 项目属性或命令行 项目 - 属性 - 编译 - 高级编译选项 - 目标 CPU 选择 x86逻辑说明目标 CPU选择直接决定进程位数。大多数桌面工具仍然建议选 x86因为 ACE 12.0 驱动在 32 位下最稳定且老项目遗留的第三方组件多半是 32 位的。如果你的工具要读很大的表、内存占用高再考虑 x64前提是目标机器必须统一装 x64 驱动。这里有一个容易混淆的点操作系统是 64 位的不代表你的进程是 64 位的。Windows 上的 32 位进程跑在 WOW64 层里它只能加载 32 位驱动。所以调试时先在开发机上确认驱动位数再看程序编译位数两个对齐了再查别的。2.3 连接串里的三个高频填写错误第一个错误是把路径写成了相对路径。Access 连接串里的Data Source默认是相对当前工作目录解析的而 VB.Net 程序的当前工作目录可能是bin\Debug也可能是桌面快捷方式指定的目录同样一段代码在不同启动方式下表现完全不一样。我一般会这样处理 用 AppDomain 基目录拼绝对路径避免工作目录漂移 Dim dbPath As String System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, data, demo.accdb) Dim connStr As String $ProviderMicrosoft.ACE.OLEDB.12.0;Data Source{dbPath};逻辑说明AppDomain.CurrentDomain.BaseDirectory是程序集所在的物理目录用Path.Combine拼出来的路径不受工作目录影响。数据文件放在程序目录下的data子文件夹里发布时整个文件夹拷走即可。如果你的程序需要写数据库文件注意这个目录必须有写权限——装在C:\Program Files下时经常会遇到权限问题。第二个错误是忘记处理连接串里的分号。数据库路径或密码里如果包含;字符连接串会被切碎导致解析失败。常见做法是把数据库文件命名规范限定为字母数字和下划线从源头规避这个字符。第三个错误是遗漏Persist Security Info。如果你在连接串里写了密码不加这个参数或设为True连接对象释放后密码仍可能被读取到存在信息泄露风险。虽然桌面工具本地使用居多但习惯上还是设置为False。3. 三套读写方案取舍Command、DataAdapter 与 Recordset 的适用边界3.1 Command 直连适合增删改和单值查询对于单条插入、更新、删除以及返回单个值的操作OleDbCommand是最直接的方式。它开销小逻辑直观不需要维护内存中的表结构。 插入一条设备台账记录 Using conn As New OleDbConnection(connStr) conn.Open() Dim sql As String INSERT INTO tbl_device(dev_name, dev_type, create_time) VALUES(?, ?, ?) Using cmd As New OleDbCommand(sql, conn) cmd.Parameters.AddWithValue(dev_name, 温度传感器-01) cmd.Parameters.AddWithValue(dev_type, T100) cmd.Parameters.AddWithValue(create_time, DateTime.Now) Dim rows As Integer cmd.ExecuteNonQuery() Console.WriteLine($影响行数: {rows}) End Using End Using逻辑说明AddWithValue是快捷写法但要注意参数顺序必须和 SQL 里的?位置一一对应。ACE 驱动不支持name之类的命名参数只认位置参数这和 SQL Server 的写法不一样很多从 SqlClient 转过来的人在这里踩坑。ExecuteNonQuery返回受影响行数主要用于 INSERT、UPDATE、DELETE。参数说明create_time建议直接用DateTime.Now传值不要在 SQL 里写Now()或GETDATE()否则可读性差且数据库服务器时间与客户端时间不一致时会产生隐性问题。这里虽然用的是本地 Access 引擎但统一由客户端传参的写法更利于日后迁移数据库。3.2 DataAdapter 填充 DataTable查询展示的正确姿势查询多条记录用于 DataGridView 展示或报表绑定时OleDbDataAdapter配合DataTable是最稳的套路。它断开连接后仍保留数据界面操作不占用数据库连接。 查询设备列表到 DataTable Dim dt As New DataTable() Using conn As New OleDbConnection(connStr) Dim sql As String SELECT id, dev_name, dev_type, create_time FROM tbl_device WHERE dev_type ? ORDER BY create_time DESC Using cmd As New OleDbCommand(sql, conn) cmd.Parameters.AddWithValue(dev_type, T100) Using da As New OleDbDataAdapter(cmd) da.Fill(dt) End Using End Using End Using DataGridView1.DataSource dt逻辑说明OleDbDataAdapter内部会自己打开连接执行查询再关闭不需要显式调用conn.Open()。Fill(dt)是核心动作它把查询结果映射到内存表。注意 Access 里的ORDER BY对文本字段排序时有个特性如果字段有多种语言混排排序结果可能和预期不一致这属于 ACE 引擎的排序规则问题后面避坑章节再细说。3.3 Recordset老系统迁移才需要考虑的 COM 组件部分存量 VB6 或 VBA 代码里习惯用ADODB.RecordsetVB.Net 里通过 COM 互操作也能用但这里不建议新代码走这条路。它的主要问题是资源释放不规范时容易造成连接泄漏而且Recordset的游标行为和 DataTable 差异很大后续 LINQ 查询、序列化都不方便。如果你的项目是需要接手的老系统则先摸清它用的是客户端游标还是服务器游标再决定是否保留这套访问方式。 兼容旧代码的 Recordset 方式仅老系统维护场景 Dim rs As New ADODB.Recordset() rs.CursorLocation ADODB.CursorLocationEnum.adUseClient rs.Open(SELECT * FROM tbl_device, connStr, ADODB.CursorTypeEnum.adOpenStatic, ADODB.LockTypeEnum.adLockReadOnly) Do While Not rs.EOF Console.WriteLine(rs.Fields(dev_name).Value.ToString()) rs.MoveNext() Loop rs.Close()逻辑说明CursorLocation设为客户端游标时数据一次性拉入本地后续遍历不占用服务器资源服务端游标则会在数据库引擎里维护游标状态Access 场景下容易把文件锁住。adLockReadOnly表示只读避免不必要的写锁。实际选型建议新项目一律用 Command DataAdapter 组合简单直接老系统维护按原代码结构走别在迁移过程中顺手改造访问层控制变量比追求完美更重要。4. 参数化查询与事务控制从拼接 SQL 到字段级精细化操作4.1 不拼接 SQL 的三个理由注入、引号和可读性很多桌面工具开发者嫌参数化查询麻烦喜欢用字符串拼接。Access 场景下拼接 SQL 主要有三个问题一是 SQL 注入风险二是文本字段里的中文引号、单引号经常把 SQL 语句弄坏三是拼接出来的语句可读性差后期维护成本高。 反面示例拼接 SQL Dim devName As String 传感器A Dim sql As String INSERT INTO tbl_device(dev_name) VALUES( devName ) devName 里的单引号会破坏语句甚至可能拼出可执行的恶意内容 正确做法参数化查询 Dim sql As String INSERT INTO tbl_device(dev_name, remark) VALUES(?, ?) Using cmd As New OleDbCommand(sql, conn) cmd.Parameters.AddWithValue(dev_name, devName) cmd.Parameters.AddWithValue(remark, remarkText) cmd.ExecuteNonQuery() End Using逻辑说明参数化查询把数值和 SQL 模板分开传输ACE 驱动内部会对参数值做类型校验和转义从机制上规避了引号破坏和注入问题。AddWithValue传字符串时驱动会自动设置长度和类型但如果你传的是 DBNull则要提前判断否则可能触发类型推断异常。参数说明AddWithValue在 ACE 驱动下偶发类型推断不准的问题尤其是日期和布尔类型。如果操作中遇到数据类型不匹配的报错改用显式构造OleDbParameter并指定OleDbType更稳妥。4.2 事务的三个边界开始、提交、回滚的时机多条 SQL 必须一起成功或一起失败时比如入库单同时写主表和明细表要用事务包裹。Access 引擎支持简单事务但要注意它的事务边界和 SQL Server 不同嵌套事务是不支持的所有操作都在一个事务上下文里排队执行。 入库单写入主表 明细表 合并提交 Using conn As New OleDbConnection(connStr) conn.Open() Dim tx As OleDbTransaction conn.BeginTransaction() Try Dim sqlMain As String INSERT INTO tb_inbound(head_no, inbound_date, supplier) VALUES(?, ?, ?) Using cmdMain As New OleDbCommand(sqlMain, conn, tx) cmdMain.Parameters.AddWithValue(head_no, RK20250101-001) cmdMain.Parameters.AddWithValue(inbound_date, DateTime.Now) cmdMain.Parameters.AddWithValue(supplier, 某供应商) cmdMain.ExecuteNonQuery() End Using Dim sqlDetail As String INSERT INTO tb_inbound_detail(head_no, item_code, qty) VALUES(?, ?, ?) Using cmdDetail As New OleDbCommand(sqlDetail, conn, tx) cmdDetail.Parameters.AddWithValue(head_no, RK20250101-001) cmdDetail.Parameters.AddWithValue(item_code, SP-1001) cmdDetail.Parameters.AddWithValue(qty, 10) cmdDetail.ExecuteNonQuery() End Using tx.Commit() Catch ex As Exception tx.Rollback() Console.WriteLine($事务回滚: {ex.Message}) End Try End Using逻辑说明BeginTransaction()必须在conn.Open()之后调用事务对象tx要传给每个OleDbCommand的构造函数否则命令在事务外执行。Commit之前所有操作只对当前连接可见其他连接读取时要么看到旧数据要么被文件锁挡住。Rollback在异常分支执行把已写入的部分全部撤销。边界注意如果事务里执行了 DDL 语句建表、加字段Access 引擎会自动提交当前事务导致后续业务操作无法回滚。通用经验是事务里尽量只放 INSERT、UPDATE、DELETE表结构变更放到程序启动时单独做。4.3 字段级精细化写操作的三个技巧第一更新时只写变化的字段。不要每次UPDATE都把所有列赋一遍值减少写锁持有时间。 只更新备注字段 Dim sql As String UPDATE tbl_device SET remark ? WHERE id ? Using cmd As New OleDbCommand(sql, conn) cmd.Parameters.AddWithValue(remark, newRemark) cmd.Parameters.AddWithValue(id, deviceId) cmd.ExecuteNonQuery() End Using第二批量更新时用ExecuteNonQuery的返回值判断实际更新行数如果返回 0说明 WHERE 条件没匹配到记录可能是数据被删或 id 不存在做对应提示。第三读取后立即更新时使用乐观并发控制。Access 没有自带版本字段机制但可以自己在表里加一个update_time字段更新时WHERE条件里带上原读取时间如果影响行数为 0说明记录已被其他客户端改过。5. 避坑清单文件锁、未指定错误与类型漂移的七次翻车记录5.1 未指定错误指向不明多半是目录权限或临时文件问题现象连接字符串正确、驱动也有但conn.Open()抛错 未指定错误0x80004005有时候带一段英文描述有时候什么都没有。原因ACE 引擎打开数据库时要创建或使用 .ldb 锁文件如果数据库所在目录没有写权限引擎会直接失败。另一个常见诱因是数据库文件被其他程序独占比如本机装的其他软件正在扫描该目录。解决先把数据库放到一个明确可写的目录比如C:\Data排除权限问题再确认没有别的进程占用文件。我一般会这样检查# 检查谁占用了 accdb 文件Windows openfiles /query | findstr /i accdb技巧如果目标机器上多个程序都要访问同一个 Access 文件建议开启共享模式连接串里加Jet OLEDB:Database Locking Mode1表示按记录锁而非整库锁。但这个参数治标不治本真正的解药是限制并发写操作的数量。5.2 文件正由另一进程使用写操作撞车现象两个客户端同时 INSERT一个成功一个报文件正由另一进程使用。原因Access 引擎写操作需要获取页面锁另一个进程的写事务还没提交时锁不释放。锁文件 .ldb 记录了占用者的计算机名和用户名但锁等待时间是有限的超过阈值直接报错。解决一是用事务把多次写操作合并成一次提交缩短锁持有时间二是把写操作集中到一个定时任务里客户端只写待处理队列由单一后台进程消费队列写库三是明确要求 Access 文件所在的网络路径是低延迟的不要在 WiFi 或跨地域链路上直接读写。5.3 类型不匹配读出来是字符串传回去要求双精度现象代码里读出qty字段赋值给 Decimal 变量一切正常但再写回 Access 时提示类型不匹配。原因ACE 驱动对字段类型的推断没有 SQL Server 严格表设计里的数字字段可能存储了混合类型数据或者AddWithValue传入的 Decimal 和字段定义的单精度不一致驱动拒绝隐式转换。解决表设计阶段统一数值字段类型避免同一列里出现整数和小数混存传参时按字段实际类型显式构造参数。 显式指定参数类型避免类型漂移 Dim p As New OleDbParameter(qty, OleDbType.Double) p.Value 10.5 cmd.Parameters.Add(p)补充日期字段同理不要传字符串形式的日期统一用DateTime类型。Access 对日期边界处理很怪1900 年以前的日期和 9999 年以后的日期都可能被转成 Null。5.4 连接泄漏Using 之外无人善后现象程序跑一段时间后越来越慢最后conn.Open()直接超时任务管理器里进程句柄数持续增长。原因OleDbConnection没有放进Using块或者只在部分代码路径里关闭连接。连接池不会无限扩展达到上限后新请求只能等待。解决所有连接对象一律用Using包裹确保异常路径也能释放如果代码里有手工conn.Close()的地方检查是否被return或Continue跳过。这里注意Using块结束后连接会归还连接池而不是真正断开但显式释放能让池管理更高效。连接池连接在 4-8 分钟无活动后会自动清理这是正常现象。5.5 LIKE 查询的通配符全换现象从 SQL Server 迁移过来的LIKE %abc%查询在 Access 里查不到结果。原因ACE 引擎默认通配符是*和?不是%和_。除非连接串里设置了 ANSI 92 SQL 模式否则通配符行为不变。解决要么在查询字符串里改用*要么显式使用ALike关键字Dim sql As String SELECT * FROM tbl_device WHERE dev_name ALike %传感器%注意ALike表示强制使用 ANSI 通配符但只有 ACE 驱动支持Jet 4.0 驱动会报语法错误。5.6 自增字段重置删了数据不回头现象删光了表里所有记录重新插入时自增 ID 不会从 1 开始。原因Access 自增字段的种子值记录在系统表里DELETE 不会重置只有压缩数据库或手动执行特定语句才会重置。解决如果必须重置自增种子把表的数据导出删除表再重建表并导入数据或者用ALTER TABLE改自增字段的种子值Access 不直接支持需要通过 ADOX 操作。如果只是想让 ID 不那么难看不必纠结重置直接继续累加即可。5.7 网络路径下文件损坏的玄学时刻现象Access 文件放在局域网共享目录里某天某个客户端写入时程序崩溃之后整个文件打不开提示不可识别的数据库格式。原因写操作过程中网络中断或进程被杀ACE 引擎缓存的数据页没来得及落盘文件头标志被写坏。解决数据库文件放本地客户端通过文件同步机制把数据汇总到服务端或者封装一层导出-合并流程而不是多个进程直接指向同一个网络文件。如果已经损坏尝试用Compact and Repair Database功能修复常见做法是用 Access 的压缩修复菜单或者在 VB.Net 里调用 DAO 引擎完成。但修复动作本身也可能失败所以定期备份比修复更重要。6. 进阶技巧批量写入提速与压缩修复的日常维护习惯Access 单条 INSERT 的速度大概在每秒几百条到一千条但如果逐条提交事务速度会断崖式下跌。批量写入提速的第一要务是减少事务提交次数——每 100 条或 500 条记录提交一次事务比每 1 条提交一次快 5-8 倍。 批量写入分批提交事务 Dim batchSize As Integer 200 Dim count As Integer 0 Dim tx As OleDbTransaction Nothing Try tx conn.BeginTransaction() For Each row In dataList Using cmd As New OleDbCommand(insertSql, conn, tx) cmd.Parameters.AddWithValue(dev_name, row.Name) cmd.Parameters.AddWithValue(dev_type, row.TypeCode) cmd.ExecuteNonQuery() End Using count 1 If count Mod batchSize 0 Then tx.Commit() tx conn.BeginTransaction() End If Next tx.Commit() Catch ex As Exception If tx IsNot Nothing Then tx.Rollback() Throw End Try逻辑说明count Mod batchSize每满 200 条提交一次这样就算中途失败最多丢失一包数据而且不用回滚全部记录。这里还有一个小技巧在同一个事务里反复创建OleDbCommand没问题但不要每次都重新拼 SQL 字符串把 SQL 模板在循环外定义好只更新参数值。压缩修复这项维护动作很多人忽略了。Access 文件删掉记录后磁盘空间不会立即释放文件体积越用越大查询性能也会跟着下降。我一般会这样处理 用 DAO 压缩修复数据库先关闭所有连接 Dim daoDb As New DAO.DBEngine() daoDb.CompactDatabase(sourcePath, destPath)逻辑说明CompactDatabase需要源文件和目标文件两个路径目标文件不能与源文件同名。压缩过程中会自动重建索引、重置自增种子、清除碎片。压缩前必须确保没有其他进程正在使用源文件否则大概率失败。从那以后我每次上线 Access 工具都会强制走一遍检查流程先确认驱动位数和编译位数一致再用参数化查询替换所有拼接 SQL接着把写操作集中到事务队列里最后把源数据库做一份备份并现场压缩一次。这套流程看着基础但对 Access 这种文件型数据库来说九成以上的现场事故都能提前避开。希望帮到你。本文还有配套的精品资源点击获取