使用DAO和Sql Server链接表的交易

6
我正在将一个传统的Access应用程序迁移到Sql Server,即DAO+链接表。我发现了一种令人沮丧的行为: 当我使用记录集对链接表进行更改时,Access会使用多个连接。多个连接意味着服务器端同时有多个事务。这些事务是独立的,不嵌套。
使用链接到.mdb文件的链接表的标准MS-Access行为是不同的。一次只有一个事务。在执行提交之前,任何运行在相同DAO.Workspace中的代码都可以看到每个数据库更改。
规则已经改变,使用客户端事务的现有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
2个回答

2
很遗憾,Microsoft关于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"并不能解决问题。我不想要多个事务,只要一个。我也不想要多个连接。 - ricardohzsz
1
这也是我对这个问题的看法。可惜啊。至少你有机会优化一下代码;-) - milivojeviCH
由于您是唯一尝试提供解决方案的人,我决定授予您赏金;也许这是一个无解的问题。无论如何,还是非常感谢您。 - Jonathan

0

对于那些仍留在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

etc etc etc.....


1
永远不要使用 DoCmd.SetWarnings False。 - Fionnuala
每当我需要使用它时,我总是确保编写一个在子程序末尾将其设置为“True”的代码。这样做不好吗? - Ethan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接