简介这是一套面向C#开发者、数据库初学者及需要快速集成Access能力的工程师的源码工程围绕Access数据库的创建与操作封装了独立类库可直接引入任意项目复用省去从零开发的时间。包内共97个文件以41个dll、13个cs源码、8个json配置、2个csproj工程文件及若干pdb、exe、md说明为主压缩包约2.53MB结构清晰便于按模块查阅。工程覆盖三种常用连接字符串获取、代码创建数据库与数据表、通用SQL执行帮助类含单条与批量插入、事务处理、指定内容查询、分页查询、更新、删除以及获取所有表名与字段名等能力并附带完整的增删查改使用示例。目前已有1996人学习下载适合希望把精力集中在业务逻辑、快速掌握Access操作套路的开发者参考借鉴。1. 用 C# 把 Access 数据库操作封装成可复用的源码工程一次讲清能不能做、值不值得做很多人第一次接到「用 C# 操作 Access 数据库」的需求时第一反应是这玩意儿太老了是不是该直接上 SQLite 或者 SQL Server。但现实是大量中小型制造企业、上位机项目、工控现场的历史数据仍然躺在.mdb或.accdb文件里客户不会为了你重构数据层他们只会说一句能不能直接读。于是你绕不开 Access 数据库也绕不开 C# 连接 Access 数据库这条链路。这个源码项目工程要解决的核心问题就三件事怎么用 C# 稳定地创建 Access 数据库文件、怎么对表做增删改查、怎么把这套逻辑封装成别人能直接拿去用的工程结构。适合谁看适合做 C# 上位机、做 MES 数据采集、做小型管理系统手里有一堆 Access 文件要处理的工程师。不适合想找 ORM 银弹的人Access 这块没有银弹只有踩坑。2. Access 数据库在 C# 里的连接原理与驱动选型为什么你的程序在 64 位机器上翻车2.1 Access 数据库不是服务它是一个文件加一套驱动Access 数据库和 SQL Server、MySQL 最大的区别在于它没有独立的数据库服务进程。.mdb和.accdb本质上就是一个文件C# 程序通过 OLE DB 或者 ODBC 驱动去读写这个文件。这意味着两件事第一你的程序不需要连网络本地文件路径对了就能跑第二驱动装没装、装的是 32 位还是 64 位直接决定你的程序能不能启动。C# 连接 Access 最常见的方式是System.Data.OleDb命名空间下的OleDbConnection连接字符串长这样// 连接 Access 2007 的 .accdb 文件 string connStr ProviderMicrosoft.ACE.OLEDB.12.0;Data SourceD:\data\demo.accdb;Persist Security InfoFalse;;如果是老的.mdb文件Provider 要换成Microsoft.Jet.OLEDB.4.0。这两个 Provider 的区别不是版本号高低而是 Jet 引擎只支持 32 位ACE 引擎有 32 位和 64 位两个版本。这就是为什么很多人在 64 位系统上跑程序报「未在本地计算机上注册 Microsoft.ACE.OLEDB.12.0 提供程序」因为你的程序编译成了 AnyCPU 或者 x64但机器上只装了 32 位的 ACE 驱动。2.2 驱动选型的三个决策点位数、版本、部署方式第一个决策点是位数匹配。你的 C# 程序目标平台如果是 x86就必须装 32 位 ACE 驱动如果是 x64就必须装 64 位 ACE 驱动。AnyCPU 在 64 位系统上默认按 64 位跑所以也会要求 64 位驱动。最稳妥的做法是在项目属性里把目标平台固定成 x86然后统一装 32 位驱动因为 32 位驱动兼容性更好很多老工控机上的其他组件也是 32 位的。第二个决策点是 ACE 版本。Microsoft.ACE.OLEDB.12.0 对应 Access 2007 及以上Microsoft.ACE.OLEDB.16.0 对应 Office 2016 及以上。如果你机器上装了 OfficeOffice 安装过程会自动带上对应版本的 ACE 驱动。但注意Office 的位数决定了 ACE 驱动的位数装了 64 位 Office 就只有 64 位 ACE装了 32 位 Office 就只有 32 位 ACE。这就是为什么有些开发机跑得好好的程序换一台机器就报驱动未注册。第三个决策点是部署方式。你不能假设客户机器上装了 Office所以发布程序时要么让客户单独安装 Access Database Engine 可再发行组件要么在安装包里静默安装。常见做法是在安装脚本里检测注册表HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Microsoft.ACE.OLEDB.12.0是否存在不存在就调用驱动安装包。提示不要试图在同一个进程里同时加载 32 位和 64 位 ACE 驱动这是不可能的。进程位数在启动时就确定了驱动必须匹配。2.3 用代码检测驱动是否可用而不是等报错与其等OleDbConnection.Open()抛异常不如在程序启动时主动检测。下面这段代码枚举本机已注册的 OLE DB Provider判断 ACE 或 Jet 是否存在using System; using System.Data.OleDb; public static class AccessDriverChecker { /// summary /// 检测指定 Provider 是否已注册 /// /summary /// param nameproviderName例如 Microsoft.ACE.OLEDB.12.0/param /// returnstrue 表示可用/returns public static bool IsProviderRegistered(string providerName) { try { // 不打开连接只枚举 Provider 列表 using (var conn new OleDbConnection()) { conn.ConnectionString $Provider{providerName};; // Open 会触发 Provider 加载失败即未注册 conn.Open(); return true; } } catch (OleDbException) { return false; } catch (InvalidOperationException) { return false; } } }这段代码的逻辑是构造一个只带 Provider 的连接字符串调用Open()。如果 Provider 未注册会抛OleDbException错误信息里包含「未注册」关键字。参数providerName传Microsoft.ACE.OLEDB.12.0或Microsoft.Jet.OLEDB.4.0。注意这个方法会短暂打开一个连接虽然不指向具体文件但某些环境下可能报「未指定数据源」所以更稳妥的做法是直接读注册表不过枚举 Provider 的方式跨版本更通用。3. 从零创建 Access 数据库文件用 ADOX 还是用 SQL 命令3.1 创建 .mdb 和 .accdb 的两种路径C# 创建 Access 数据库文件有两条路。第一条是用 ADOXMicrosoft ADO Ext. for DDL and Security组件通过 COM 互操作调用CatalogClass.Create()方法。第二条是用OleDbConnection连接到一个不存在的文件某些 Provider 会自动创建但 ACE 驱动默认不会自动创建.accdbJet 驱动也不会自动创建.mdb。所以实际项目中创建数据库文件基本都用 ADOX。ADOX 的引用方式是在项目里添加 COM 引用Microsoft ADO Ext. 2.8 for DDL and Security然后在代码里using ADOX;。注意 ADOX 是 COM 组件部署时不需要额外注册但需要目标机器有对应的 ACE 或 Jet 驱动。3.2 创建数据库并建表的完整代码下面这段代码演示用 ADOX 创建.accdb文件然后用 SQL 建一张表using System; using ADOX; public static class AccessDbCreator { /// summary /// 创建 Access 数据库文件 /// /summary /// param namefilePath例如 D:\data\demo.accdb/param /// param nameproviderMicrosoft.ACE.OLEDB.12.0 或 Microsoft.Jet.OLEDB.4.0/param public static void CreateDatabase(string filePath, string provider) { if (System.IO.File.Exists(filePath)) throw new InvalidOperationException(文件已存在不要覆盖); // CatalogClass 是 ADOX 的核心类 var catalog new CatalogClass(); try { // 连接字符串只指定 Provider不指定文件 string connStr $Provider{provider};Data Source{filePath};; catalog.Create(connStr); } finally { // 释放 COM 对象避免文件被占用 System.Runtime.InteropServices.Marshal.ReleaseComObject(catalog); } } }创建完文件后用OleDbConnection打开并执行CREATE TABLEusing System.Data.OleDb; public static void CreateTable(string filePath, string provider) { string connStr $Provider{provider};Data Source{filePath};; using (var conn new OleDbConnection(connStr)) { conn.Open(); string sql CREATE TABLE DeviceLog ( Id AUTOINCREMENT PRIMARY KEY, DeviceName TEXT(50), Value DOUBLE, LogTime DATETIME ); using (var cmd new OleDbCommand(sql, conn)) { cmd.ExecuteNonQuery(); } } }参数说明AUTOINCREMENT是 Access 的自增主键写法对应 SQL Server 的IDENTITY。TEXT(50)是 Access 的变长字符串不写长度默认 255。DOUBLE对应 C# 的doubleDATETIME对应DateTime。注意 Access 的 SQL 方言和 T-SQL 有差异比如不支持NVARCHAR用TEXT或MEMO。3.3 创建时的三个硬性约束第一文件路径不能包含特殊字符尤其是#和OLE DB 连接字符串里这些字符有特殊含义会导致解析失败。第二目标目录必须存在ADOX 不会自动创建目录。第三如果文件已存在catalog.Create()会抛异常所以要先判断。我一般会在创建前用File.Exists检查如果存在就抛业务异常让调用方决定是覆盖还是换名。注意ADOX 创建.accdb时如果机器上只有 32 位 ACE 驱动而你的程序是 64 位catalog.Create()会报「未注册提供程序」。这不是 ADOX 的问题是驱动位数不匹配。4. 增删改查的封装参数化查询、事务和批量插入的落地写法4.1 为什么必须用参数化查询以及 Access 的参数占位符Access 的 OLE DB 参数占位符是?不是name。这意味着你不能像 SQL Server 那样写WHERE Id id只能写WHERE Id ?然后按顺序Add参数。顺序错了结果就错了这是 Access 开发里最常见的翻车点之一。下面是一个封装好的查询方法using System.Data; using System.Data.OleDb; public static DataTable Query(string filePath, string provider, string sql, params OleDbParameter[] parameters) { string connStr $Provider{provider};Data Source{filePath};; using (var conn new OleDbConnection(connStr)) using (var cmd new OleDbCommand(sql, conn)) { if (parameters ! null parameters.Length 0) cmd.Parameters.AddRange(parameters); var dt new DataTable(); using (var adapter new OleDbDataAdapter(cmd)) { adapter.Fill(dt); } return dt; } }调用时var dt Query(D:\data\demo.accdb, Microsoft.ACE.OLEDB.12.0, SELECT * FROM DeviceLog WHERE DeviceName ? AND LogTime ?, new OleDbParameter(p1, PLC-01), new OleDbParameter(p2, DateTime.Today));注意OleDbParameter的名字其实不重要Access 只看顺序。但为了代码可读性我一般还是给个名字。参数类型要匹配DateTime传DateTime不要传字符串否则 Access 可能按文本比较导致日期范围查询结果不对。4.2 事务和批量插入Access 的性能边界在哪Access 单条插入的性能很差一千条数据逐条ExecuteNonQuery可能要几十秒。正确做法是用事务包起来或者用OleDbDataAdapter批量更新。下面是一个事务批量插入的写法public static void BatchInsert(string filePath, string provider, DataTable data) { string connStr $Provider{provider};Data Source{filePath};; using (var conn new OleDbConnection(connStr)) { conn.Open(); using (var tran conn.BeginTransaction()) using (var cmd new OleDbCommand( INSERT INTO DeviceLog (DeviceName, Value, LogTime) VALUES (?, ?, ?), conn, tran)) { // 预定义参数循环里只改值 cmd.Parameters.Add(new OleDbParameter(name, OleDbType.VarWChar)); cmd.Parameters.Add(new OleDbParameter(value, OleDbType.Double)); cmd.Parameters.Add(new OleDbParameter(time, OleDbType.Date)); foreach (DataRow row in data.Rows) { cmd.Parameters[0].Value row[DeviceName]; cmd.Parameters[1].Value row[Value]; cmd.Parameters[2].Value row[LogTime]; cmd.ExecuteNonQuery(); } tran.Commit(); } } }参数说明OleDbType.VarWChar对应 Access 的TEXTOleDbType.Double对应DOUBLEOleDbType.Date对应DATETIME。事务提交前所有插入都在一个事务里Access 会写日志文件速度比逐条提交快一个数量级。但注意Access 单表数据量超过 10 万行后查询性能会明显下降这不是 C# 能解决的是 Jet/ACE 引擎的边界。4.3 更新和删除的注意事项更新和删除的 SQL 写法没有特殊之处但有两个坑。第一Access 不支持UPDATE ... FROM语法多表关联更新要写成子查询。第二删除时如果表有自增主键删除后自增值不会重置这是正常行为不要试图去改系统表。下面是一个更新示例public static int Update(string filePath, string provider, string deviceName, double newValue) { string connStr $Provider{provider};Data Source{filePath};; using (var conn new OleDbConnection(connStr)) using (var cmd new OleDbCommand(UPDATE DeviceLog SET Value ? WHERE DeviceName ?, conn)) { cmd.Parameters.Add(new OleDbParameter(value, newValue)); cmd.Parameters.Add(new OleDbParameter(name, deviceName)); conn.Open(); return cmd.ExecuteNonQuery(); // 返回受影响行数 } }ExecuteNonQuery返回受影响行数可以用来判断更新是否命中记录。如果返回 0说明WHERE条件没匹配到任何行不是报错是业务上没找到。5. 避坑与排查Access 数据库在 C# 项目里的五个血泪教训5.1 报「未在本地计算机上注册 Microsoft.ACE.OLEDB.12.0 提供程序」现象程序在开发机跑得好好的部署到客户机器上直接崩异常信息就是这句。原因客户机器没装 ACE 驱动或者装的位数和程序不匹配。开发机装了 OfficeOffice 带了驱动客户机可能只装了 WPS 或者什么都没装。解决在安装包里附带 Access Database Engine 可再发行组件安装前检测注册表。如果客户机是 64 位系统但装的是 32 位 Office你的程序必须编译成 x86。最稳的组合是程序目标平台 x86安装 32 位 ACE 驱动。5.2 插入中文变成乱码或者问号现象INSERT进去的中文用 Access 打开看是乱码或者直接变成???。原因连接字符串没有指定字符集或者参数类型用了OleDbType.VarChar而不是VarWChar。VarChar是 ANSI 字符集VarWChar是 Unicode。解决参数类型统一用OleDbType.VarWChar连接字符串里可以加Jet OLEDB:Global Partial Bulk Ops2之类的优化参数但字符集问题主要靠参数类型解决。5.3 多线程同时写同一个 Access 文件导致文件锁死现象上位机多个线程同时往 Access 写数据跑一段时间后报「文件已被占用」或者「无法锁定文件」。原因Access 是文件级锁不是行级锁。多个连接同时写ACE 引擎会尝试加锁并发高了就冲突。解决所有写操作串行化用一个全局队列或者lock对象包住。如果并发量确实大考虑换 SQLite 或者 SQL Server。Access 的定位就是单机、低并发不要拿它当高并发数据库用。5.4 查询日期范围结果不对现象WHERE LogTime ?传DateTime.Today结果查出来的数据少了或者多了。原因Access 的日期存储包含时间部分DateTime.Today是当天 00:00:00如果数据里有当天更早的时间会被排除。另外如果参数类型传成了字符串Access 会按文本比较2024-1-1和2024-01-01排序结果不同。解决参数类型用OleDbType.Date查询范围用和组合比如LogTime ? AND LogTime ?传DateTime.Today和DateTime.Today.AddDays(1)。5.5 程序退出后 Access 文件被锁定无法删除或重命名现象程序关了但.accdb文件删不掉提示被占用。原因OleDbConnection没有Dispose或者OleDbDataReader没有关闭。ACE 引擎会保持文件句柄直到连接池释放。解决所有OleDbConnection、OleDbCommand、OleDbDataReader都用using包起来。如果用了连接池可以在连接字符串里加OLE DB Services-4禁用连接池但更推荐的做法是确保using到位。另外ADOX 的CatalogClass是 COM 对象用完必须Marshal.ReleaseComObject。6. 把源码工程做成可复用的 NuGet 包或模板项目的具体技巧6.1 工程结构怎么分层才不乱一个可复用的 Access 操作工程我一般分成三层Access.Core放连接管理、驱动检测、异常封装Access.Repository放具体的增删改查和事务Access.Models放实体类。这样别人引用的时候如果只想用连接管理就只引Access.Core。如果标题里的「源码项目工程」是要给别人直接用的那入口类要足够简单比如一个AccessDbFactory类构造函数传文件路径和 Provider然后暴露Query、Execute、BatchInsert几个方法就够了。6.2 用接口隔离方便以后换 SQLite虽然现在用 Access但保不齐哪天客户要换 SQLite。所以我会定义一个IDbProvider接口public interface IDbProvider { DataTable Query(string sql, params object[] parameters); int Execute(string sql, params object[] parameters); void BeginTransaction(); void Commit(); void Rollback(); }Access 的实现类叫AccessDbProviderSQLite 的实现类叫SqliteDbProvider。上层业务代码只依赖IDbProvider换数据库时只改工厂方法。这个技巧在工控上位机项目里特别有用因为不同现场的历史数据格式可能不一样。6.3 验证方法用单元测试覆盖驱动检测和增删改查不要靠手动点按钮验证。写一个 xUnit 或者 NUnit 测试项目每个测试方法创建一个临时.accdb文件跑完删除。下面是一个测试示例[Fact] public void CreateInsertQuery_ShouldReturnSameValue() { string tempFile Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() .accdb); string provider Microsoft.ACE.OLEDB.12.0; try { AccessDbCreator.CreateDatabase(tempFile, provider); CreateTable(tempFile, provider); BatchInsert(tempFile, provider, BuildTestData()); var dt Query(tempFile, provider, SELECT COUNT(*) FROM DeviceLog); Assert.Equal(100, Convert.ToInt32(dt.Rows[0][0])); } finally { if (File.Exists(tempFile)) File.Delete(tempFile); } }这个测试覆盖了创建、建表、批量插入、查询四个环节。参数说明Guid.NewGuid()保证文件名不冲突Path.GetTempPath()避免权限问题。跑完在finally里删文件防止临时文件堆积。如果测试报驱动未注册说明 CI 环境没装 ACE 驱动需要在 CI 脚本里加安装步骤。6.4 一个我踩过的坑不要用SELECT *做长期维护早期图省事查询全用SELECT *后来表结构加了字段DataTable 的列顺序变了下游解析代码直接崩。后来我改成显式列名虽然写起来麻烦但表结构变更时影响可控。这个习惯在 Access 项目里尤其重要因为 Access 改表结构不像 SQL Server 那么方便很多时候是直接加字段列顺序不可控。6.5 值不值得做Access 方案的适用边界如果你的项目是单机、数据量在 10 万行以内、并发不超过 5 个写入线程Access 加 C# 这套方案完全够用开发速度快部署简单客户不需要装数据库服务。但如果数据量上百万、多客户端同时写、需要网络访问趁早换 SQLite 或者 SQL Server。我一般会在项目初期就跟客户确认数据量和并发量如果超了边界直接建议换方案不要硬上 Access后期迁移成本更高。希望帮到你。本文还有配套的精品资源点击获取