使用窗体更新表中的数据非常容易。只需针对想要更新的表的字段,在窗体上放置控件。例如,下图显示了 frmSales。frmSales 上的控件可以更新 tblSales、tblSalesLineitems 和 tblSalesPayments 中的数据,因为这些字段直接绑定到 frmSales 上的控件。

但有时,你可能希望更新某个未显示在窗体上的字段。例如,在 frmSales 中输入信息时,tblCustomers 中最后销售日期的字段 ( LastSalesDate ) 应该进行更新以反映联系人购买某种产品的最新日期。当输入一笔销售时,LastSalesDate 字段的值将是 frmSales 上 txtSaleDate 控件的值。

由于联系人的最后销售日期引用 frmSales 上的 txtSaleDate 控件,因此,你不希望用户必须两次输入该值。从理论上来说,可将 LastSalesDate 字段作为一个计算字段放置在窗体上,在用户输入销售日期后进行更新,但显示该字段将会比较混乱,并与当前销售的商品不相关。

处理 tblCustomer 中的 LastSaleDate 字段更新的最佳方式是使用 VBA 过程。可使用 VBA 代码来更新某条记录中的各个字段、添加新记录或删除记录。

☛ 使用 ADO 更新记录中的字段

可使用 AfterUpdate 事件过程更新 LastSalesDate ( 如下所示 )。该过程使用 ADO 语法直接对 tblCustomers 进行操作。

Private Sub Form_AfterUpdate()

  Dim rsContacts As ADODB.Recordset
  Dim sSQL As String
    
  On Error GoTo ErrHandler
    
  If Not IsNull(Me.cboCustomerID.Value) Then
    If Not IsNull(Me.txtSaleDate.Value) Then
        
      sSQL = "SELECT * FROM tblCustomers WHERE CustomerID = " _
          & Me.cboCustomerID.Value
      
      Set rsContacts = New ADODB.Recordset
      rsContacts.Open sSQL, CurrentProject.Connection, _
          adOpenDynamic, adLockOptimistic
      
      If Not rsContacts.EOF Then
          rsContacts!LastSalesDate = Me.txtSaleDate.Value
          rsContacts.Update
      End If
      
      rsContacts.Close
      Set rsContacts = Nothing
    
    End If
  End If

ErrExit:
  Exit Sub
    
ErrHandler:
  MsgBox "Error is " & Err.Description & " in " & Me.Name
    
End Sub

用于访问和操纵 Access 数据库中的数据的编程语法是 ADO。ADO 定义很多不同对象,每个对象都有一组属性和方法,用于执行各种面向数据的操作。

ADO 不是一种编程语言,而是专为数据访问而设计的一种 VBA 语法。语法仅指在 VBA 代码中用于完成特定任务的单词和短语。

ADO 是从各种位置访问数据的通用方法。到目前为止你已经看到的示例显示了如何使用 Access 更新本地 Access 数据库中的数据。所有表、查询、窗体和报表都存储在位于桌面上的一个文件夹中或文件服务器上单个 Access 数据库文件中。但作为常规的数据库开发工具,Access 可与所有种类的数据库进行交互。你可以在一个 Access 数据库中开发窗体和报表,而这些窗体和报表从可能位于本地桌面或远程文件服务器的另一个 Access 数据库中获取其数据。你甚至可以链接到非 Access 服务器数据库,例如 Oracle 和 SQL Server,与链接到 Access 数据库一样轻松。

作为数据访问接口,ADO 允许编写程序以操纵本地或远程数据库中的数据。使用 ADO,你可以执行很多数据库功能,包括查询、更新、数据类型转换、编制索引、锁定、验证以及事务管理。

下面提供一个过程代码片段,显示如何使用 ADO Recordset 对象打开表:

Dim rs As ADODB.Recordset

Set rs = New ADODB.Recordset

rs.ActiveConnection = CurrentProject.Connection
rs.Source = "tblContacts"
rs.CursorType = adOpenDynamic
rs.LockType = adLockOptimistic

rs.Open

ADO Recordset 对象提供了 Open 方法用于从表或查询检索数据。记录集只不过是数据库表中的一组记录或查询返回的一组记录。

Open 方法具有四个参数,如下所述:

Source:要打开的数据源。Source 可以是某个表的名称、某个查询的名称或检索记录的 SQL 语句。当引用表时,该表可以是本地表,也可以是链接表。

ActiveConnection:与某个数据库连接。连接是与数据库内部对象进行接触的通信线路。CurrentProject.Connection 引用当前 Access 数据库。

CursorType:光标指的是指向记录的一个或一组指针。可将光标认为是 ADO 跟踪记录的方式。根据用于检索数据的属性设置,ADO 光标可能会仅在记录中向前移动 ( adOpenForwardOnly ) 或允许前后移动 ( adOpenDynamic )。动态光标 ( adOpenDynamic ) 允许双向移动,而 adOpenForwardOnly 只允许向前移动。指定 Recordset 对象中使用的光标类型如下表所示:

LockType:确定在更新时 ADO 如何锁定记录。adLockOptimistic 允许其他用户处理通过 ADO 代码锁定的记录,而 adLockPessimistic 会将记录完全锁定,在对记录数据进行更改时,其他用户无法对该记录进行处理。LockType 的种类说明如下:

① adLockBatchOptimistic :指示乐观批量更新。 对于批量更新模式来说是必需的。许多应用程序会一次提取大量的行,然后需要进行协调更新,其中包括要插入、更新或删除的整个行集。 使用批处理游标时,只需往返服务器一次,进而提高更新性能和减少网络流量。 借助批处理游标库,可创建静态游标,然后断开与数据源的连接。 此时,可对行进行更改,然后重新连接,并在批处理中将更改发布到数据源。

② adLockOptimistic :指示提供程序使用乐观锁定 – 仅在你调用 Update 方法时锁定记录。 这意味着,其他用户可能会在你编辑记录与调用 Update 期间更改数据,这会产生冲突。 在冲突可能性较低或者可轻松解决冲突的情况下,请使用这种锁类型。

③ adLockPessimistic :指示悲观锁定 ( 逐条记录 )。 提供程序会采取必要操作来确保成功编辑记录,通常是在编辑后立即锁定数据源中的记录。 当然,这意味着在你开始编辑后,记录对其他用户不可用,直到你调用 Update 来释放锁定为止。在无法对数据进行并发更改的系统中(例如在预留系统中),请使用该类型的锁。

④ adLockReadOnly :指示只读记录。 不能更改数据。 只读锁是“最快”的锁类型,因为它不需要服务器维护记录上的锁。

⑤ adLockUnspecified :不指定锁的类型。

通过更精简的方式重新编写 ADO 语句,如下所示:

Dim adRs As ADODB.Recordset

Set adRs = New ADODB.Recordset

adRs.Open "tblCustomers", CurrentProject.Connection, adOpenDynamic, adLockOptimistic

上面的示例中,记录集属性作为 Open 语句的一部分进行设置。每种语法都是正确的,具体选择哪一种,完全取决于开发人员的喜好。此外,由于我们直接访问表,因此,无法为数据指定 ORDER BY。数据很可能会按照某种无法预测的顺序返回。

下面列出了另一个根据 CustomerID 提取单笔记录的示例:

Dim adRs As ADODB.Recordset

Set adRs = New ADODB.Recordset

adRs.ActiveConnection = CurrentProject.Connection
adRs.Source = _
  "SELECT * FROM tblCustomers WHERE CustomerID = 17"
adCursorType = adOpenDynamic
adLockType = adLockOptimistic

adRs.Open

上例中,Source 属性是一个 SQL SELECT 语句。用于提取记录的 SQL 语句会根据 CustomerID 条件返回单笔记录。由于 LockType 属性设置为 adLockOptimistic,因此,用户可以更改记录中的数据。

CursorType 和 LockType 都是可选的。如果不指定 CursorType 或 LockType,默认情况下,ADO 会将记录集创建为 adOpenFoewardOnly / adLockReadOnly 类型记录集。这种类型的记录集无法更新。如果需要对记录集中的数据进行更改,必须了解各种 CusorType 和 LockType 组合以及它们如何影响记录集的功能。

使用 ADO 时,几乎完全是通过 Recordset 对象与数据进行交互。记录集由包含字段的行组成,就像数据库表一样。打开记录集后,便可以开始处理其行和字段中的值。

当打开可更新的记录集 ( 通过使用 adOpenDynamic 或 adOpenKeySet 光标类型并指定 adLockOptimistic 锁定类型 ) 时,该记录集将在编辑模式下打开。

在数据表视图中打开的表与记录集的一个主要差别在于,记录集不提供其包含的数据的可视表示形式。数据表会为你提供数据的行和列,甚至包含列标题,使你可以了解基础表中字段的名称。

记录集仅存在于内存中。没有一种简单的方式可以对记录集中的数据进行可视化。作为开发人员,必须始终了解字段名称、行计数以及其他对应程序非常重要的数据属性。

在使用数据表和记录集时,只有一条记录处于活动状态。在数据表中,活动记录通过行中的颜色差异来指示。记录集不具备这种可视辅助功能,因此,必须始终了解记录集中的哪条记录是当前的活动记录。

ADO 和 DAO 都提供了很多用于跟踪记录集中记录的方式,以及用于在记录集中进行移动的各种技术。此外,了解记录集中的字段名称以及修改每个字段中的数据也非常容易。

在更改记录集的任何字段中的数据之前,需要确保位于要编辑的记录中。当某个记录集打开时,当前记录是该记录集中的第一条记录。如果记录集不包含任何记录,则该记录集的 EOF 属性为 True。

警告:

如果尝试在不包含任何记录的记录集中操纵数据,将发生运行错误。请务必在打开记录集后立即检查 EOF 属性的值,如下所示:

Set adRs = New ADODB.Recordset
adRs.Open "tblCustomers" ...

If Not adRs.EOF Then
   'Okay to process records
End If

如果代码移动到 EOF ( MoveNext ) 或 BOF ( MovePrevious ) 以外,将发生错误。编写的代码应该始终在执行移动方法后检查 EOF 和 BOF 属性。

更改记录后,使用记录集的 Update 方法将该记录提交到数据库。Update 方法会将数据从内存缓冲区复制到记录集,覆盖原始记录。整个记录都将被替换,而不仅是更新的字段。当然,记录集中的其他记录不会受到更新操作的影响。

当移动到另一条记录或者关闭记录集时,对 ADO 记录集所作的更改将自动保存。此外,如果关闭记录集或者结束声明该记录集或父数据库的过程,也会保存编辑的记录。但是,你应该使用 Update 方法以提高代码的可读性和可维护性。

使用记录集的 CancelUpdate 方法可以取消对 ADO 记录集的待定更改。如果撤销对某条记录所做的更改非常重要,必须在移动到 ADO 记录集中的另一条记录前发出 CancelUpdate 方法,因为移离某条记录会提交更改,从而无法再进行撤销。

处理完记录集以后将其关闭是一种非常好的做法,需要注意的是,Recordset 对象被显式设置为无任何对象 ( 如:Set rsContacts = Nothing ) 以将记录集从内存中清除。省略这个重要的步骤可能会导致 ” 内存泄漏 “,因为如果不显式地将 ADO 对象设置为 Nothing 并放弃,那么它会一直保留在内存中。

☛ 添加新记录

使用 ADO 向表中添加一笔记录,就如同更新一条记录一样轻松。使用 AddNew 方法可以向表中添加一条新纪录。下面的代码显示了一个用于向 tblCustomers 表中添加新客户的 ADO 过程:

Public Sub AddNewContact(sFirstName As String, sLastName As String)
  
    Dim adRs As ADODB.Recordset
    Set adRs = New ADODB.Recordset
    
    adRs.Open "tblCustomerContacts", CurrentProject.Connection, _
        adOpenDynamic, adLockOptimistic
    
    With adRs
        .AddNew  'Add new record
        
        'Add data:
        .Fields("LastName").Value = sLastName
        .Fields("FirstName").Value = sFirstName
        
        .Update  'Commit changes
    End With
    
    adRs.Close
    Set adRs = Nothing

End Sub

如示例中所示,使用 AddNew 方法类似于使用 ADO 编辑记录集数据。AddNew 会为新纪录创建一个缓冲区。执行 AddNew 后,你将会为新纪录的字段分配值。Update 方法将会将新纪录添加到记录集的末尾处,然后添加到基础表中。

☛ 删除记录

要从表中删除记录,可使用 ADO 方法 Delete。下面的代码显示了用于从 tblCustomers 表中删除记录的过程:

Public Sub DeleteContact(ContactID As Long)
  
    Dim adRs As ADODB.Recordset
    Dim sSQL As String
    
    Set adRs = New ADODB.Recordset
    
    sSQL = "SELECT * FROM tblCustomerContacts " _
        & "WHERE ID = " & ContactID & ";"
    
    adRs.Open sSQL, CurrentProject.Connection, _
        adOpenDynamic, adLockOptimistic
    
    With adRs
        If Not .EOF Then
            .Delete  'Delete the record
        End If
    End With
    
    adRs.Close
    Set adRs = Nothing
  
End Sub

注意:

请不要在 Delete 方法后面紧跟 Update。一旦执行了 Delete 方法,记录便会立即从记录集中永久删除。

使用 ADO 删除记录不会触发删除对话框。一般来讲,使用 ADO 代码对数据所做的更改不需要进行确认,因为确认会中断用户的工作流。这意味着,作为开发人员,应该由你来负责确保删除操作正确无误,然后再继续进行操作。记录删除后,便无法再撤销对基础表所做的更改。但是,Access 仍然会强制实施参照完整性。如果尝试删除违反参照完整性的记录,将会收到错误。

☛ 删除多个表中的相关记录

当编写 ADO 代码以删除记录时,需要了解应用程序的关联关系。包含要删除的记录的表可能会与另一个表具有一对多关系。

在下面的表的关系图中,tblSales 具有两个关联的相关表,分别是 tblSalesLineItems 和 tblSalesPayments。

” 编辑关系 ” 对话框显示了 tblSales 和 tblSalesLineItems 之间的关系的设置状况。关系类型为一对多 ( 1 : M ),并强制实施参照完整性。一对多关系意味着父表 ( tblSales ) 每条记录可能在子表 ( tblSalesLineItems ) 中具有一条或多条对应的记录。父表中的每条记录必须唯一,比如,不能有两条 InvoiceNumber 、SalesDate 和其他信息完全相同的销售记录。

在一对多关系中,每条子记录 ( 在 tblSalesLineItems 中 ) 必须与父表 ( tblSales ) 中的一条记录 ( 并且只能有一条记录 ) 相关联。但是,tblSales 中的每条销售记录可能与 tblSalesLineItems 中的多条记录相关联。

当针对一对多关系强制实施参照完整性时,即会告诉 Access,对于 tblSales 表中的记录,如果 tblSalesLineItems 表中存在具有相同发票编号值的记录,则不能将其删除。如果 Access 遇到违反参照完整性的删除请求,那么它会显示一条错误消息,并且删除操作将会取消,除非在 ” 编辑关系 ” 对话框中启用了级联删除。

在绝大多数情况下,最好使用 Active 字段 ( Yes/No 数据类型 ) 来指示父记录状态。当订单下达时,Active 字段设置为 Yes,仅当订单已经取消或完成时,才会设置为 No。你也可能会向 tblSales 表中添加一个 CancellationDate 字段,并将其设置订单被取消的日期。如果 CancellationDate 为空,则表示订单尚未取消。

当编写 ADO 代码以删除记录时,需要首先检查以确定包含要删除的记录的表与数据库中的其他任何表之间是否存在任何一对多关系。如果存在相关表,在 Access 允许你删除父表中的记录之前,需要先删除相关表中的记录。

幸运的是,你可以编写单个过程以删除相关表和父表中的记录。下面是 frmSales 中的 cmdDelete 命令按钮对应的代码:

Private Sub cmdDelete_Click()

  Dim lAnswer As Long
  
  Const sSQL_DELPMTS As String = _
    "DELETE * FROM tblSalesPayments WHERE InvoiceNumber = "
  Const sSQL_DELLINE As String = _
    "DELETE * FROM tblSalesLineitems WHERE InvoiceNumber = "
    
  If Me.NewRecord Then
    Me.Undo
  Else
      
    lAnswer = MsgBox("Are you sure you want to delete this Invoice?", _
      vbQuestion + vbYesNo, "Delete Invoice")
    
    If lAnswer = vbYes Then
        
      'Delete all payments for this invoice
      CurrentDb.Execute sSQL_DELPMTS & Me.InvoiceNumber.Value & ";"
      
      'Delete all line items for this invoice
      CurrentDb.Execute sSQL_DELLINE & Me.InvoiceNumber.Value & ";"
      
      'Now delete the invoice record
      DoCmd.RunCommand acCmdSelectRecord
      DoCmd.RunCommand acCmdDeleteRecord
    
    End If
  
  End If
  
End Sub

cmdDelete_Click 事件过程将删除 tblSalesPayments、tblSalesLineItems 和 tblSales 中具有与当前发票编号匹配的发票编号记录。

cmdDelete_Click 中的第一个语句 ( If Me.NewRecord Then ) 使用 NewRecord 属性来确定当前销售记录是否为新纪录。如果是新纪录,Me.Undo 将回滚对记录所做的更改。如果当前记录不是新纪录,该过程将显示一个消息框,确认用户确实想要删除该记录。如果用户单击 ” 是 ” 按钮,该过程便会将该记录从表中删除。

通过两个常量 sSQL_DELPMTS 和 sSQL_DELLINE 保存分别用于在 tblSalesPayments 和 tblSalesLineItems 中查找和删除具有与 frmSales 上发票编号匹配的记录的 SQL 语句。发票编号连接到常量的结尾,并且以参数形式传递到 CurrentDb 的 Execute 方法。可将查询的名称或 SQL 语句作为参数传递到 Execute 方法。Execute 方法只是运行指定的查询或 SQL 语句。

注意:

如果查询或 SQL 语句包含 WHERE 子句,但 Execute 方法找不到任何满足 WHERE 条件的记录,不会发生任何错误。但是,如果查询或 SQL 语句包含无效的语法或无效的字段或表名,Execute 方法将失败,并引发错误。

在删除 tblSalesPayments 和 tblSalesLineItems 表中的记录后,便可以删除 tblSales 表中的记录。

Access 范例:Access 2016 VBA 代码编写范例