使用 DAO 和 Sql Server 链接表的事务
Posted
技术标签:
【中文标题】使用 DAO 和 Sql Server 链接表的事务【英文标题】:Transactions using DAO and Sql Server linked tables 【发布时间】:2012-09-09 19:56:33 【问题描述】:我正在将一个经典的 Access 应用程序迁移到 Sql Server,即 DAO+链接表。
我发现了一个令人沮丧的行为:当我在链接表上使用记录集进行更改时,Access 使用多个连接。多个连接意味着服务器端一次有多个事务。这些交易是独立的。没有嵌套。
使用链接表到 .mdb 文件的标准 MS-Access 行为是不同的。一次只有一笔交易。在执行提交之前,在同一 DAO.Workspace 中运行的任何代码都可以看到每个 db 更改。
规则已更改,使用客户端事务的现有 DAO 代码将失败。
如果我使用打开为 dbOpenDynaset 的记录集添加或更新记录,任何尝试读取它们的代码都将失败:找不到新记录并查看原始状态的现有记录。为什么?因为操作是在多个独立的事务中进行的
执行提供的示例代码,sql profiler 将显示使用不同的事务 ID 进行不同的操作。
我已经使用 ADO 对此进行了测试,一切正常。但是有数千行代码。
除了使用 ADO 重写代码之外,还有其他解决方案吗?
我可以修改标准访问行为吗? (使用读取未提交的隔离级别,指示不打开新连接,...)
以下代码重现了该问题。很简单:
1.- 在现有记录上打开记录集 2.- 添加新记录 3.- 尝试阅读最近添加的记录
如果我在 (1) 中使用 dbOpenDynaset,我将在 (3) 中看不到新记录。
我正在使用 Acc-2010、.accdb 格式文件和 Sql Server 2008 R2
谢谢。
Private Sub test0()
Dim bResult As Boolean
Dim bUseTrans As Boolean 'New record added in transaction
Dim rsExist As DAO.Recordset2 'Dummy recordset
Dim tRecordsetExist As DAO.RecordsetTypeEnum 'Dummy recordset type:
' with dbOpenDynaset fail.
' Any other works fine
Dim rs2Add As DAO.Recordset
Dim rs2Read As DAO.Recordset 'Used to read recently added record
Dim tRecordset2Read As DAO.RecordsetTypeEnum 'Recordset type used to read new record. Doesn't affect
Dim bTranInitiated As Boolean 'Track if we are in transaction
Dim lngExistingNumber As Long
Dim lngNewNumber As Long
Dim lngNewID As Long
Dim strSQL As String
On Error GoTo HandleErr
'Invoices table definition in SS. Table is linked as [dbo_Invoices]:
' CREATE TABLE [dbo].[Invoices](
' [IdInvoice] [int] IDENTITY(1,1) NOT NULL,
' [InvoiceNumber] [int] NOT NULL,
' [InvoiceDescription] [varchar](50) NOT NULL,
' CONSTRAINT [PK_Invoices] PRIMARY KEY CLUSTERED
' (
' [IdInvoice] Asc
' )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
' ) ON [PRIMARY]
Set wks = DBEngine.Workspaces(0)
Set dbs = wks.Databases(0)
bUseTrans = True 'Without transaction everything works well
tRecordsetExist = dbOpenDynaset 'Dummy recordset type:
' dbOpenDynaset makes fail.
' Any other works fine
tRecordset2Read = dbOpenForwardOnly 'Does not affect
lngExistingNumber = 12001
lngNewNumber = -lngExistingNumber
'Clean previous runs of the test and make sure that referenced invoice exists.
dbs.Execute "Delete from dbo_Invoices Where InvoiceNumber = " & lngNewNumber, dbFailOnError Or dbSeeChanges
On Error Resume Next
strSQL = "Insert Into dbo_Invoices (InvoiceNumber, InvoiceDescription) " & _
" Values (" & lngExistingNumber & ", 'Original invoice' )"
dbs.Execute strSQL, dbFailOnError Or dbSeeChanges
On Error GoTo HandleErr
If bUseTrans Then
wks.BeginTrans
bTranInitiated = True
End If
strSQL = "Select IdInvoice, InvoiceNumber from dbo_Invoices " & _
" Where InvoiceNumber = " & lngExistingNumber
If tRecordsetExist = dbOpenDynaset Then
Set rsExist = dbs.OpenRecordset(strSQL, dbOpenDynaset, dbSeeChanges)
Else
Set rsExist = dbs.OpenRecordset(strSQL, tRecordsetExist)
End If
If rsExist.BOF And rsExist.EOF Then
Err.Raise vbObjectError, , "Original invoice " & lngExistingNumber & " not found"
End If
Set rs2Add = dbs.OpenRecordset("Select * from dbo_Invoices", dbOpenDynaset, dbAppendOnly Or dbSeeChanges)
rs2Add.AddNew
rs2Add!InvoiceNumber = lngNewNumber
rs2Add!InvoiceDescription = "Invoice anulation, ref " & lngExistingNumber
rs2Add.Update
'After executing .Update rs2Add goes to .EOF. This action reposition the recordset on the new record
rs2Add.Move 0, rs2Add.LastModified
lngNewID = rs2Add!IdInvoice
Debug.Print "New record added: IdInvoice = " & rs2Add!IdInvoice & ", InvoiceNumber = " & rs2Add!InvoiceNumber
'Try to read the new record
strSQL = "Select * from dbo_Invoices Where IdInvoice = " & lngNewID
If tRecordset2Read = dbOpenDynaset Then
Set rs2Read = dbs.OpenRecordset(strSQL, dbOpenDynaset, dbSeeChanges)
Else
Set rs2Read = dbs.OpenRecordset(strSQL, tRecordset2Read)
End If
If (rs2Read.BOF And rs2Read.EOF) Then
Err.Raise vbObjectError, , "rs2Read: Not found using IdInvoice = " & lngNewID
End If
Debug.Print "New record found with IdInvoice = " & rs2Read!IdInvoice
rs2Read.Close
bResult = True
ExitHere:
If Not wks Is Nothing Then
If bTranInitiated Then
If bResult Then
wks.CommitTrans
Else
wks.Rollback
End If
bTranInitiated = False
End If
End If
On Error Resume Next
If Not rs2Add Is Nothing Then
rs2Add.Close
Set rs2Add = Nothing
End If
If Not rs2Read Is Nothing Then
rs2Read.Close
Set rs2Read = Nothing
End If
Exit Sub
HandleErr:
Dim e As Object
If Err.Description Like "ODBC*" Then
For Each e In DBEngine.Errors
MsgBox e.Description, vbCritical
Next
Else
MsgBox Err.Description, vbCritical
End If
bResult = False
Resume ExitHere
Resume
End Sub
【问题讨论】:
【参考方案1】:不幸的是,微软对Workspace.IsolateODBCTrans
属性声明如下:
http://msdn.microsoft.com/en-us/library/office/bb208483(v=office.12).aspx
某些 ODBC 服务器,例如 Microsoft SQL Server,不允许在单个连接上同时进行事务。如果您需要一次针对此类数据库挂起多个事务,请在打开每个 Workspace 后立即将 IsolateODBCTrans 属性设置为 True。 这会强制为每个 Workspace 建立单独的 ODBC 连接。
不确定这是否会帮助您决定要做什么。
【讨论】:
谢谢。但我放弃了。我们决定重写。 “IsolateODBCTrans”没有解决。我不想要多于一笔交易,就一个。我不想要多个连接。 这也是我对这个问题的看法。遗憾。至少您可以进行一些更改以优化代码 ;-) 因为你是唯一一个试图为这个问题提供解决方案的人,所以我决定奖励你;)也许这是一个没有解决方案的问题。还是谢谢。【参考方案2】:对于保留在 mdb 中的那些表,您可以继续使用 dao。但是对于像这样的 sqlserver 表(链接表): 全局 objConn 作为新的 ADODB.Connection
在例程中:
Dim rst As ADODB.Recordset
DoCmd.SetWarnings False
If objConn.State <> adStateOpen Then
MsgBox ("Connection to SQL server has not been made. Please exit and resolve problem.")
Exit Sub
End If
Set rst = New ADODB.Recordset
Dim stdocname As String
rst.Open "tblbilling", objConn, adOpenDynamic, adLockPessimistic
等等等等……
【讨论】:
永远不要使用 DoCmd.SetWarnings False @Fionnuala 每当我需要使用它时,我总是确保我写一个在子末尾设置它“True”的...这不好吗?以上是关于使用 DAO 和 Sql Server 链接表的事务的主要内容,如果未能解决你的问题,请参考以下文章
作为 SQL Server 的前端访问 - ADO 与 DAO?
MS SQL Server 中是不是有任何方法可以使用某种链接将表的一列映射到另一个数据库?
如何使用 Access VBA 更新所有 ODBC 链接的 SQL Server 表的服务器名称
SQL Server:从链接到另一个表的 ID 列动态创建列