2012-09-17 5 views
6

Je migre une application Access classique vers Sql Server, c'est-à-dire des tables DAO + liées.Transactions utilisant des tables liées DAO et Sql Server

J'ai trouvé un comportement fustrant: lorsque j'apporte des modifications en utilisant des jeux d'enregistrements sur des tables liées, Access utilise plusieurs connexions. Plus d'une connexion signifie plus d'une transaction à la fois sur le serveur. Ces transactions sont indépendantes. Pas imbriqué

Le comportement standard d'accès MS utilisant des tables liées à un fichier .mdb est différent. Il n'y a qu'une seule transaction à la fois. Chaque changement de base de données est visible par tout code qui s'exécute dans le même DAO.Workspace avant d'exécuter la validation.

Les règles ont été modifiées et le code DAO existant utilisant les transactions côté client échouera. Si j'ajoute ou met à jour un enregistrement en utilisant un jeu d'enregistrements ouvert comme dbOpenDynaset, tout code essayant de les lire après échouera: Ne trouve pas de nouveaux enregistrements et voit les enregistrements existants dans leur état d'origine. Pourquoi? Étant donné que les opérations sont exécutées dans plusieurs transactions indépendantes

En exécutant l'exemple de code fourni, sql profiler vous montre que différentes opérations sont effectuées avec différents ID de transaction.

J'ai testé cela en utilisant ADO et tout fonctionne bien. Mais il y a des milliers de lignes de code.

Existe-t-il une solution autre que le code de réécriture utilisant ADO?

Puis-je modifier le comportement standard d'accès? (lire le niveau d'isolement non-validé, indiquer de ne pas ouvrir de nouvelles connexions, ...)

Le code ci-dessous reproduit le problème. Il est très simple:

Ouvrir une 1.- sur recordset existant enregistrement
Ajouter un nouvel enregistrement 2.-
Essayez de lire 3.- enregistrer

récemment ajouté Si j'utilise dans dbOpenDynaset (1), i ne verra pas de nouveau record dans (3).

J'utilise Acc-2010, les fichiers de format .accdb et Sql Server 2008 R2

Merci.

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 

Répondre

2

Malheureusement, Microsoft indique ce qui suit à propos de la propriété Workspace.IsolateODBCTrans: http://msdn.microsoft.com/en-us/library/office/bb208483(v=office.12).aspx

Certains serveurs ODBC, tels que Microsoft SQL Server, ne permettent pas les transactions simultanées sur une seule connexion. Si vous devez effectuer plusieurs transactions à la fois en attente avec une telle base de données, définissez la propriété IsolateODBCTrans sur True sur chaque espace de travail dès que vous l'ouvrez. Cela force une connexion ODBC distincte pour chaque espace de travail.

Vous ne savez pas si cela vous aidera à décider quoi faire.

+0

Merci. Mais j'ai abandonné. Nous avons décidé de réécrire. Le "IsolateODBCTrans" ne résout pas. Je ne veux pas plus d'une transaction, juste oine. Je ne veux pas plus d'une connexion. – ricardohzsz

+1

C'est aussi ce que j'ai pensé du problème. Pitié. Au moins vous obtenez un changement pour optimiser le code un peu ;-) – milivojeviCH

+0

Comme vous êtes le seul qui a essayé d'offrir une solution à ce problème, j'ai décidé de vous récompenser avec la prime;) Peut-être que c'est un problème sans solution. Merci quand même. – Jonathan

0

Vous pouvez continuer à utiliser dao pour les tables qui restent dans le mdb. Cependant, pour les tables sqlserver (tables liées) comme ceci: Global objConn As New ADODB.Connexion

et dans la routine:

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

Ne jamais utiliser DoCmd.SetWarnings False – Fionnuala

+0

@Fionnuala chaque fois que j'ai besoin de l'utiliser, je m'assure toujours d'en écrire un qui le définit "True" à la fin du sous ... Est-ce pas ok? – Ethan

Questions connexes