news 2026/7/23 22:00:46

链接表能删了:Access 直连 SQL Server,DAO 绑窗体 + ADO 参数查询完整代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
链接表能删了:Access 直连 SQL Server,DAO 绑窗体 + ADO 参数查询完整代码

摘要:后台是 SQL Server 却不想用链接表?本文介绍 DAO 直连绑定窗体和 ADO 参数化查询两种方案,配完整可运行代码,直接拿走用。access开发|access培训|access框架|请添加edonsoft。

Hi,大家好!

上一篇讲了 Access 和 SQL Server 的差别。有读者看完之后问了一个很实际的问题:后台已经是 SQL Server,前端 Access 用的是链接表,但现在不想用链接表了,有没有别的办法让窗体继续能查数据、能编辑?

这个需求我遇到过不止一次,原因也各不相同——有的是网络环境里链接表刷新太慢,有的是服务器迁移了链接路径失效,还有的是想做更严格的权限控制,不想让 Access 直接"看到"表结构。

办法有两种。一种是 DAO 直连:用DBEngine.OpenDatabase在 VBA 里打开 ODBC 连接,把记录集直接绑给窗体,新增、修改、删除全部照常,体验和链接表几乎没差别。另一种是 ADO 参数化查询:用ADODB.Command带参数跑 SQL,把结果填进列表框或子窗体,更适合复杂筛选、存储过程调用,以及需要控制事务的保存操作。两种都给完整代码。

先说 DAO 直连

DAO(Data Access Objects)是 Access 的原生数据访问接口,很多人不知道它其实也能不靠链接表直接连 ODBC 数据源。做法是用DBEngine.OpenDatabase打开一个 ODBC 连接,然后拿到的DAO.Recordset直接赋给窗体的Recordset属性,窗体就有数据了。

这种方式最大的好处是:窗体保持绑定状态,新增、修改、删除照常用,Access 的导航按钮、记录锁都还在,几乎和链接表的使用体验一样,只是数据源换成了 ODBC 直连。

DAO 方案完整代码

新建一个标准模块,命名modSQLConn,把下面的连接字符串函数放进去,后面窗体代码会用到:

Option Compare Database Option Explicit ' 返回 SQL Server 无 DSN 连接字符串 ' 根据实际情况修改 SERVER、DATABASE Public Function SQLConnStr() As String SQLConnStr = "ODBC;" & _ "DRIVER={ODBC Driver 18 for SQL Server};" & _ "SERVER=SQL01;" & _ "DATABASE=SalesDb;" & _ "Trusted_Connection=Yes;" & _ "Encrypt=Yes;" & _ "TrustServerCertificate=Yes;" End Function

然后打开需要绑定的窗体(设计视图),把窗体的记录源清空,在窗体模块里写:

Option Compare Database Option Explicit ' 注意:必须声明在模块顶部,不能放在 Form_Load 里 ' 生命周期和窗体绑定,窗体关闭前不能释放 Private mDb As DAO.Database Private mRs As DAO.Recordset Private Sub Form_Load() Dim sql As String ' 打开 ODBC 直连,不用预先建 DSN ' dbDriverNoPrompt:连接失败直接报错,不弹驱动选择框 Set mDb = DBEngine.OpenDatabase( _ "", dbDriverNoPrompt, False, SQLConnStr()) ' 写你真正需要的查询,必须包含主键列,否则记录集不可更新 sql = "SELECT OrderID, CustomerID, OrderDate, Amount, Remark " & _ "FROM dbo.Orders " & _ "ORDER BY OrderDate DESC;" ' dbOpenDynaset:动态集,支持编辑 ' dbSeeChanges:表有 IDENTITY 自增列时必须加,否则新增报错 Set mRs = mDb.OpenRecordset(sql, dbOpenDynaset, dbSeeChanges) ' 把记录集绑给窗体,完成后窗体控件自动按字段名匹配 Set Me.Recordset = mRs End Sub Private Sub Form_Unload(Cancel As Integer) ' 窗体关闭时释放资源,顺序不能反 If Not mRs Is Nothing Then mRs.Close Set mRs = Nothing End If If Not mDb Is Nothing Then mDb.Close Set mDb = Nothing End If End Sub

窗体里的文本框控件名字只要和查询字段名一致(不区分大小写),绑定自动生效,不需要手动设控件来源,但是你的控件一定要添加控件来源

有三个地方容易出问题,写这段代码之前先说清楚。

mDbmRs必须声明在窗体模块顶部,不能放在Form_Load里。放进过程里就成了局部变量,Form_Load跑完就释放,窗体打开之后数据随时会变成空白,或者弹出"对象无效"的错误。这是我见过最多人踩的地方,而且报错时机不固定,有时候立刻报,有时候要等用户翻几页记录才出。

查询必须包含主键,且查询本身可更新。两表联接、带GROUP BY、带DISTINCT的查询基本上都是只读的,这种结果集赋给窗体之后能看数据,但改不了。如果窗体只需要显示,问题不大;如果需要编辑,就得把查询拆开,或者改用 ADO 加存储过程来保存。

dbSeeChanges这个参数,SQL Server 表有IDENTITY自增列时必须加。不加的话,新增一条记录之后 Access 找不到刚插入的那行,会弹"找不到记录"。加上之后 Access 在 INSERT 完成后会自动定位到新行,这个问题就消失了。

加筛选条件

如果窗体需要按条件筛选(比如按订单日期范围),不要拼接 SQL 字符串,改用Recordset.Filter

Private Sub btnFilter_Click() Dim d1 As String Dim d2 As String ' 取文本框里的日期,转成 SQL Server 认识的格式 d1 = Format(Me.txtDateFrom, "yyyy-mm-dd") d2 = Format(Me.txtDateTo, "yyyy-mm-dd") ' Filter 条件用字段名,日期用单引号括起来 mRs.Filter = "OrderDate >= '" & d1 & "' AND OrderDate <= '" & d2 & "'" ' 用筛选后的克隆集重新绑窗体 Set Me.Recordset = mRs.OpenRecordset() End Sub

如果筛选条件变化很大,比如字段都不固定,更简单的做法是重新执行mDb.OpenRecordset,用新 SQL 替换旧的,再重新赋给Me.Recordset

再说 ADO 参数化查询

ADO(ActiveX Data Objects)连接 SQL Server 时走 OLE DB 或 ODBC,写法和连接其他数据库基本一样。我一般在这几种情况下选 ADO 而不是 DAO:筛选条件多、带多个参数的查询;需要调用 SQL Server 存储过程;执行写入操作时需要拿回影响行数或者输出参数。

ADO 的一个重要习惯是参数化——用?占位符传值,不把变量直接拼进 SQL 字符串。日期格式、单引号转义这些问题直接绕开,SQL 注入的风险也没有了。我见过不少人图省事用"WHERE CustomerID = " & Me.cboCustomer这种拼法,字段是数字还好,一旦遇到字符串或者日期,调试起来很麻烦。

ADO 公共模块

用 ADO 之前要先在 Access 引用库里勾上Microsoft ActiveX Data Objects。VBE 菜单 → 工具 → 引用,找到Microsoft ActiveX Data Objects 6.1 Library(或者 2.8,装了什么版本就选哪个),勾上确定。没有这一步,代码里的ADODB.Connection会报"用户自定义类型未定义"。

新建标准模块modADO

Option Compare Database Option Explicit ' 建立 ADO 连接,成功返回 ADODB.Connection,失败返回 Nothing ' 调用方负责关闭和释放 Public Function ADO_Connect() As ADODB.Connection Dim conn As ADODB.Connection Set conn = New ADODB.Connection ' OLE DB Provider for SQL Server ' MSOLEDBSQL 是微软 2018 年后推荐的新驱动,需独立安装: ' https://learn.microsoft.com/zh-cn/sql/connect/oledb/download-oledb-driver-for-sql-server ' 如果没装,换成 SQLNCLI11(SQL Server 2012+ 自带): ' Provider=SQLNCLI11; ' ' OLE DB Windows 集成验证用 Integrated Security=SSPI ' (不是 ODBC 的 Trusted_Connection=Yes,那个 OLE DB 不认) ' 改用 SQL 账号的话替换为:UID=sa;PWD=yourpwd; conn.ConnectionString = _ "Provider=MSOLEDBSQL;" & _ "Server=SQL01;" & _ "Database=SalesDb;" & _ "Integrated Security=SSPI;" & _ "TrustServerCertificate=yes;" On Error GoTo ConnErr conn.Open Set ADO_Connect = conn Exit Function ConnErr: Set conn = Nothing Set ADO_Connect = Nothing MsgBox "连接 SQL Server 失败:" & Err.Description, vbCritical End Function

如果机器上没有 MSOLEDBSQL,也可以换成 ODBC 方式:"Provider=MSDASQL;DRIVER={ODBC Driver 18 for SQL Server};SERVER=SQL01;...",两种写法功能上没区别,Driver 18 装了就能用。

参数化查询 Demo:按客户和日期范围查询订单

' 查询订单,结果填入列表框 lstOrders ' lstOrders 的列数需要提前设好,列宽也要配好 Private Sub btnQuery_Click() Dim conn As ADODB.Connection Dim cmd As ADODB.Command Dim rs As ADODB.Recordset Dim rows As String Set conn = ADO_Connect() If conn Is Nothing Then Exit Sub Set cmd = New ADODB.Command cmd.ActiveConnection = conn ' 参数用 ? 占位,不拼字符串 cmd.CommandText = _ "SELECT OrderID, CustomerName, OrderDate, Amount " & _ "FROM dbo.Orders " & _ "WHERE CustomerID = ? " & _ " AND OrderDate BETWEEN ? AND ? " & _ "ORDER BY OrderDate DESC;" cmd.CommandType = adCmdText ' 按顺序追加参数:类型、方向、大小、值 ' adInteger, adDate, adDate cmd.Parameters.Append cmd.CreateParameter("@CustID", adInteger, adParamInput, , CLng(Me.cboCustomer)) cmd.Parameters.Append cmd.CreateParameter("@D1", adDate, adParamInput, , CDate(Me.txtDateFrom)) cmd.Parameters.Append cmd.CreateParameter("@D2", adDate, adParamInput, , CDate(Me.txtDateTo)) Set rs = cmd.Execute ' 用 ValueList 方式填列表框 ' 也可以改用 rs 直接赋给子窗体的 Recordset rows = "" Do While Not rs.EOF rows = rows & rs("OrderID") & ";" & _ rs("CustomerName") & ";" & _ Format(rs("OrderDate"), "yyyy-mm-dd") & ";" & _ Format(rs("Amount"), "#,##0.00") & ";" rows = rows & Chr(10) rs.MoveNext Loop rs.Close conn.Close Set rs = Nothing Set cmd = Nothing Set conn = Nothing Me.lstOrders.RowSourceType = "Value List" Me.lstOrders.RowSource = rows End Sub

参数化执行 Demo:保存一条订单

' 保存窗体上的订单数据到 SQL Server ' 成功返回 True,失败返回 False Public Function SaveOrder( _ ByVal customerID As Long, _ ByVal orderDate As Date, _ ByVal amount As Currency, _ ByVal remark As String _ ) As Boolean Dim conn As ADODB.Connection Dim cmd As ADODB.Command Set conn = ADO_Connect() If conn Is Nothing Then SaveOrder = False Exit Function End If Set cmd = New ADODB.Command cmd.ActiveConnection = conn cmd.CommandText = _ "INSERT INTO dbo.Orders (CustomerID, OrderDate, Amount, Remark) " & _ "VALUES (?, ?, ?, ?);" cmd.CommandType = adCmdText cmd.Parameters.Append cmd.CreateParameter("@CustID", adInteger, adParamInput, , customerID) cmd.Parameters.Append cmd.CreateParameter("@Date", adDate, adParamInput, , orderDate) cmd.Parameters.Append cmd.CreateParameter("@Amount", adCurrency, adParamInput, , amount) cmd.Parameters.Append cmd.CreateParameter("@Remark", adVarWChar, adParamInput, 500, remark) On Error GoTo SaveErr cmd.Execute conn.Close Set cmd = Nothing Set conn = Nothing SaveOrder = True Exit Function SaveErr: MsgBox "保存失败:" & Err.Description, vbCritical If Not conn Is Nothing Then conn.Close Set cmd = Nothing Set conn = Nothing SaveOrder = False End Function

调用的地方很简单:

Private Sub btnSave_Click() If SaveOrder(Me.cboCustomer, Me.txtDate, Me.txtAmount, Me.txtRemark) Then MsgBox "保存成功", vbInformation Me.txtAmount = Null Me.txtRemark = Null End If End Sub

调用 SQL Server 存储过程

如果保存逻辑放在 SQL Server 存储过程里——比如要做库存扣减、写操作日志、或者要保证多张表同时写入——ADO 也可以直接调,把CommandType换成adCmdStoredProc就行。

假设 SQL Server 端有这样一个存储过程:

CREATEPROCEDUREdbo.usp_AddOrder@CustomerIDINT,@OrderDateDATE,@AmountDECIMAL(18,2),@RemarkNVARCHAR(500),@NewOrderIDINTOUTPUT-- 返回新生成的订单号ASBEGINSETNOCOUNTON;INSERTINTOdbo.Orders(CustomerID,OrderDate,Amount,Remark)VALUES(@CustomerID,@OrderDate,@Amount,@Remark);SET@NewOrderID=SCOPE_IDENTITY();END

VBA 这边这样调:

Public Function CallAddOrder( _ ByVal customerID As Long, _ ByVal orderDate As Date, _ ByVal amount As Currency, _ ByVal remark As String, _ ByRef newOrderID As Long _ ) As Boolean Dim conn As ADODB.Connection Dim cmd As ADODB.Command Set conn = ADO_Connect() If conn Is Nothing Then CallAddOrder = False Exit Function End If Set cmd = New ADODB.Command cmd.ActiveConnection = conn cmd.CommandText = "dbo.usp_AddOrder" cmd.CommandType = adCmdStoredProc ' 改这里 ' 输入参数 cmd.Parameters.Append cmd.CreateParameter("@CustomerID", adInteger, adParamInput, , customerID) cmd.Parameters.Append cmd.CreateParameter("@OrderDate", adDate, adParamInput, , orderDate) cmd.Parameters.Append cmd.CreateParameter("@Amount", adCurrency, adParamInput, , amount) cmd.Parameters.Append cmd.CreateParameter("@Remark", adVarWChar, adParamInput, 500, remark) ' 输出参数:adParamOutput,不传值,执行后从这里读回来 cmd.Parameters.Append cmd.CreateParameter("@NewOrderID", adInteger, adParamOutput, , 0) On Error GoTo SPErr cmd.Execute newOrderID = cmd.Parameters("@NewOrderID").Value ' 读取存储过程返回的新订单号 conn.Close Set cmd = Nothing Set conn = Nothing CallAddOrder = True Exit Function SPErr: MsgBox "调用存储过程失败:" & Err.Description, vbCritical If Not conn Is Nothing Then conn.Close Set cmd = Nothing Set conn = Nothing CallAddOrder = False End Function

这种写法,Access 前端不需要知道存储过程里写了什么,库存怎么扣、日志怎么写、哪几张表要联动,都在 SQL Server 里处理,VBA 只管传参数、拿结果。

两种方案怎么选

我自己的习惯是这样区分的:

场景推荐方案
窗体需要连续编辑多条记录,新增、修改、删除频繁DAO 直连绑定
筛选条件复杂、带多个参数的查询ADO 参数化查询
需要调用存储过程,或有事务、返回输出参数ADO Command
只读报表、统计汇总ADO 查询结果填子窗体或列表框
核心业务录入,有严格的校验和审计要求非绑定窗体 + ADO 调存储过程保存

两种方案在同一个项目里混用很常见。比如查询列表用 ADO,点进某条记录打开编辑窗体用 DAO 绑定——查询这边灵活,编辑这边省事,各取所长。

还有一种更轻量的方式是传递查询(Pass-through Query),在 Access 查询设计器里直接建,连接字符串写 SQL Server ODBC 地址,SQL 在服务器端跑,结果返回给 Access。适合只读查询或者不需要在 VBA 里动态拼参数的场景。这个我之前专门写过,这里不重复了。

去掉链接表之后,DAO 方案改动最小,和原来链接表绑窗体的体验几乎没差别,迁移起来也快。ADO 的价值在往后走——等你开始需要存储过程、输出参数、事务控制,那套代码不用大改,加参数就行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/23 22:00:41

openEuler 22.03 NFS + mergerfs 存储池部署与性能调优完整文档

&#x1f4d8; openEuler 22.03 NFS mergerfs 存储池部署与性能调优完整文档 文档概述 本文档基于实际生产环境部署经验&#xff0c;详细记录了在 openEuler 22.03 系统上&#xff0c;使用 NFS mergerfs 构建超大容量统一存储池的完整流程。特别针对 NFS 写入性能瓶颈 进行了…

作者头像 李华
网站建设 2026/7/23 22:00:23

2026抗逆风稳产方案:3项核心技术让作物挺过大风

引言近年来&#xff0c;极端天气事件频发&#xff0c;大风倒伏已成为威胁农作物稳产高产的重要因素之一。据统计&#xff0c;我国每年因倒伏造成的粮食损失可达总产量的5%-10%&#xff0c;其中玉米、小麦等大田作物尤为严重。面对这一挑战&#xff0c;现代农业技术正从多个维度…

作者头像 李华
网站建设 2026/7/23 21:59:43

AI动漫短剧制作全流程技术解析

1. AI动漫短剧创作的技术全景第一次接触AI动漫短剧制作时&#xff0c;我被这个领域的技术栈复杂度震惊了。从最初的文字脚本到最终成片输出&#xff0c;整个过程涉及自然语言处理、图像生成、语音合成、视频剪辑等多个技术模块的协同工作。经过半年多的实战摸索&#xff0c;我总…

作者头像 李华
网站建设 2026/7/23 21:58:52

AI学术写作助手:从智能大纲到文献矩阵

1. 项目概述&#xff1a;当学术写作遇上AI助手去年帮导师审阅本科课程论文时&#xff0c;一个现象让我印象深刻&#xff1a;超过60%的学生在文献综述部分直接复制粘贴&#xff0c;连参考文献格式都懒得调整。这背后反映的不仅是学术诚信问题&#xff0c;更是新手面对学术写作时…

作者头像 李华
网站建设 2026/7/23 21:58:42

基于YOLOv10的体育场馆设备智能检测系统设计与实现

1. 项目背景与需求分析体育场馆作为大型公共活动场所&#xff0c;其场地设备的正常运行直接关系到赛事质量和观众安全。传统人工巡检方式存在效率低、漏检率高的问题&#xff0c;特别是在大型赛事期间&#xff0c;设备故障可能引发严重后果。这个毕业设计项目正是针对这一痛点&…

作者头像 李华
网站建设 2026/7/23 21:55:22

TVA驱动的具身智能迭代逻辑(12)

前沿技术探索&#xff1a;AI智能体视觉&#xff08;TVA&#xff0c;Transformer-based Vision Agent&#xff09;是依托Transformer架构与“因式智能体”理论所构建的颠覆性工业视觉技术&#xff0c;是集深度强化学习&#xff08;DRL&#xff09;、卷积神经网络&#xff08;CNN…

作者头像 李华