x64 microsoft español engine descargar ms-access transactions workspace

ms-access - español - microsoft.ace.oledb.12.0 download



Transacciones de MS Access(Jet), espacios de trabajo (3)

Las transacciones se usan definiendo un espacio de trabajo (no tiene que ser uno nuevo) y luego comienza la transacción en ese espacio de trabajo, haciendo lo que tiene que hacer con él, y luego comprometiendo la transacción si todo está bien. Código esquelético:

On Error GoTo errHandler Dim wrk As DAO.Workspace Set wrk = DBEngine(0) '' use default workspace wrk.BeginTrans [do whatever] If [conditions are met] Then wrk.CommitTrans Else wrk.Rollback End If errHandler: Set wrk = Nothing exitRoutine: '' do whatever you''re going to do with errors wrk.Rollback Resume errHandler

Ahora, dentro del bloque donde [haga lo que sea], puede pasar el espacio de trabajo y las bases de datos y los conjuntos de registros a las subrutinas, pero la estructura de control de nivel superior debe permanecer en un solo lugar.

Su código no hace eso; en cambio, depende de variables globales. LAS VARIABLES GLOBALES SON MALAS. No los uses En su lugar, pase variables privadas como parámetros a las subrutinas que desea operar en ellas. También diría que nunca pase el espacio de trabajo, solo pase los objetos que ha creado con el espacio de trabajo.

Una vez que haya absorbido eso, tal vez lo ayude a explicar lo que se supone que debe lograr su código (que no tengo la menor idea de leerlo), y luego podemos aconsejarle sobre lo que está haciendo mal .

Tengo problemas para comprometer una transacción (usando Access 2003 DAO). Está actuando como si nunca hubiera llamado a BeginTrans. Recibo el error 3034 en CommitTrans, "Intentó confirmar o deshacer una transacción sin primero comenzar una transacción" ; y los cambios se escriben en la base de datos (presumiblemente porque nunca se envolvieron en una transacción). Sin embargo, BeginTrans se ejecuta, si lo atraviesas.

  • Lo estoy ejecutando dentro del entorno de acceso usando el espacio de trabajo DBEngine (0).
  • Las tablas a las que agrego registros se abren a través de una conexión de base de datos Jet (a la misma base de datos) y usando DAO.Recordset.AddNew / Update.
  • La conexión se abre antes de iniciar BeforeTrans.
  • No estoy haciendo nada extraño en el medio de la transacción, como cerrar / abrir conexiones o múltiples espacios de trabajo, etc.
  • Hay dos niveles de transacción anidados. Básicamente está envolviendo inserciones múltiples en una transacción externa, por lo que si falla, todas fallarán. Las transacciones internas se ejecutan sin errores, es la transacción externa que no funciona.

Aquí hay algunas cosas que he estudiado y descartado:

  • La transacción se distribuye en varios métodos y BeginTrans y CommitTrans (y Rollback) están en diferentes lugares. Pero cuando probé una prueba simple de ejecutar una transacción de esta manera, no parece que esto debería importar.

  • Pensé que tal vez la conexión de la base de datos se cierra cuando se sale del alcance local, aunque tengo otra referencia "global" (nunca estoy seguro de lo que DAO hace con las conexiones dbase para ser honesto). Pero parece que este no es el caso, justo antes del commit, la conexión y sus recordssets están vivos (puedo verificar sus propiedades, EOF = False, etc.)

  • Mi CommitTrans y Rollback se realizan dentro de devoluciones de llamadas de eventos. (Básicamente, un programa analizador está lanzando un evento ''onLoad'' al final del análisis sintáctico, que estoy procesando ya sea confirmando o anulando las inserciones que hice durante el procesamiento, dependiendo de si ocurrió algún error). Sin embargo, de nuevo, intentando una prueba simple, no parece que esto debería importar.

¿Alguna idea de por qué esto no funciona para mí?

Gracias.

EDITAR 25 de mayo

Aquí está el código (simplificado). Los puntos clave que tienen que ver con la transacción son:

  • El espacio de trabajo es DBEngine (0), referenciado dentro de la variable pública (global) APPSESSION .
  • La conexión de la base de datos se abre en LoadProcess.cache a continuación, consulte la línea Set db = APPSESSION.connectionTo(dbname_) .
  • BeginTrans se llama en LoadProcess.cache.
  • Se llama a CommitTrans en la devolución de llamada process__onLoad.
  • La reversión se llama en el proceso de devolución de llamada inválida inválida.
  • Las actualizaciones del conjunto de registros se realizan en process__onLoadRow, logLoadInit y logLoad

Eric

''------------------- ''Application globals ''------------------- Public APPSESSION As DAOSession ''------------------ '' Class LoadProcess ''------------------ Private WithEvents process_ As EventedParser Private errs_ As New Collection Private dbname_ As String Private rawtable_ As String Private logtable_ As String Private isInTrans_ As Integer Private raw_ As DAO.Recordset Private log_ As DAO.Recordset Private logid_ As Variant Public Sub run ''--- pre-load cache resetOnRun '' resets load state variables per run, omitted here logLoadInit Set process_ = New EventedParser ''--- load process_.Load End Sub '' raised once per load() if any row invalid Public Sub process__onInvalid(filename As String) If isInTrans_ Then APPSESSION.Workspace.Rollback End Sub '' raised once per load() if all rows valid, after load Public Sub process__onLoad(filename As String) If errs_.Count > 0 Then logLoadFail filename, errs_ Else logLoadOK filename End If If isInTrans_ Then APPSESSION.Workspace.CommitTrans End Sub '' raised once per valid row '' append data to raw_ recordset Public Sub process__onLoadRow(row As Dictionary) On Error GoTo Err_ If raw_ Is Nothing Then GoTo Exit_ DAOext.appendFromHash raw_, row, , APPSESSION.Workspace Exit_: Exit Sub Err_: '' runtime error handling done here, code omitted Resume Exit_ End Sub Private Sub cache() Dim db As DAO.Database '' TODO raise error If Len(dbname_) = 0 Then GoTo Exit_ Set db = APPSESSION.connectionTo(dbname_) '' TODO raise error If db Is Nothing Then GoTo Exit_ Set raw_ = db.OpenRecordset(rawtable_), dbOpenDynaset) Set log_ = db.OpenRecordset(logtable_), dbOpenDynaset) APPSESSION.Workspace.BeginTrans isInTrans_ = True Exit_: Set db = Nothing End Sub '' Append initial record to log table Private Sub logLoadInit() Dim info As New Dictionary On Error GoTo Err_ '' TODO raise error? If log_ Is Nothing Then GoTo Exit_ With info .add "loadTime", Now .add "loadBy", CurrentUser End With logid_ = DAOext.appendFromHash(log_, info, , APPSESSION.Workspace) Exit_: Exit Sub Err_: '' runtime error handling done here, code omitted Resume Exit_ End Sub Private Sub logLoadOK(filename As String) logLoad logid_, True, filename, New Collection End Sub Private Sub logLoadFail(filename As String, _ errs As Collection) logLoad logid_, False, filename, errs End Sub '' Update log table record added in logLoadInit Private Sub logLoad(logID As Variant, _ isloaded As Boolean, _ filename As String, _ errs As Collection) Dim info As New Dictionary Dim er As Variant, strErrs As String Dim ks As Variant, k As Variant On Error GoTo Err_ '' TODO raise error? If log_ Is Nothing Then GoTo Exit_ If IsNull(logID) Then GoTo Exit_ For Each er In errs strErrs = strErrs & IIf(Len(strErrs) = 0, "", vbCrLf) & CStr(er) Next Er With info .add "loadTime", Now .add "loadBy", CurrentUser .add "loadRecs", nrecs .add "loadSuccess", isloaded .add "loadErrs", strErrs .add "origPath", filename End With log_.Requery log_.FindFirst "[logID]=" & Nz(logID) If log_.NoMatch Then ''TODO raise error Else log_.Edit ks = info.Keys For Each k In ks log_.Fields(k).Value = info(k) Next k log_.Update End If Exit_: Exit Sub Err_: '' runtime error handling done here, code omitted Resume Exit_ End Sub ''------------- '' Class DAOExt ''------------- '' append to recordset from Dictionary, return autonumber id of new record Public Function appendFromHash(rst As DAO.Recordset, _ rec As Dictionary, _ Optional map As Dictionary, _ Optional wrk As DAO.workspace) As Long Dim flds() As Variant, vals() As Variant, ifld As Long, k As Variant Dim f As DAO.Field, rst_id As DAO.Recordset Dim isInTrans As Boolean, isPersistWrk As Boolean On Error GoTo Err_ '' set up map (code omitted here) For Each k In rec.Keys If Not map.Exists(CStr(k)) Then _ Err.Raise 3265, "appendFromHash", "No field mapping found for [" & CStr(k) & "]" flds(ifld) = map(CStr(k)) vals(ifld) = rec(CStr(k)) ifld = ifld + 1 Next k If wrk Is Nothing Then isPersistWrk = False Set wrk = DBEngine(0) End If wrk.BeginTrans isInTrans = True rst.AddNew With rst For ifld = 0 To UBound(flds) .Fields(flds(ifld)).Value = vals(ifld) Next ifld End With rst.Update Set rst_id = wrk(0).OpenRecordset("SELECT @@Identity", DAO.dbOpenForwardOnly, DAO.dbReadOnly) appendFromHash = rst_id.Fields(0).Value wrk.CommitTrans isInTrans = False Exit_: On Error GoTo 0 If isInTrans And Not wrk Is Nothing Then wrk.Rollback If Not isPersistWrk Then Set wrk = Nothing Exit Function Err_: '' runtime error handling, code omitted here Resume Exit_ End Function ''----------------- '' Class DAOSession (the part that deals with the workspace and dbase connections) ''----------------- Private wrk_ As DAO.workspace Private connects_ As New Dictionary Private dbs_ As New Dictionary Public Property Get workspace() As DAO.workspace If wrk_ Is Nothing Then If DBEngine.Workspaces.Count > 0 Then Set wrk_ = DBEngine(0) End If End If Set workspace = wrk_ End Property Public Property Get connectionTo(dbname As String) As DAO.database connectTo dbname Set connectionTo = connects_(dbname) End Property Public Sub connectTo(dbname As String) Dim Cancel As Integer Dim cnn As DAO.database Dim opts As Dictionary Cancel = False '' if already connected, use cached reference If connects_.Exists(dbname) Then GoTo Exit_ If wrk_ Is Nothing Then _ Set wrk_ = DBEngine(0) '' note opts is a dictionary of connection options, code omitted here Set cnn = wrk_.OpenDatabase(dbs_(dbname), _ CInt(opts("DAO.OPTIONS")), _ CBool(opts("DAO.READONLY")), _ CStr(opts("DAO.CONNECT"))) '' Cache reference to dbase connection connects_.Add dbname, cnn Exit_: Set cnn = Nothing Exit Sub End Sub


OK, después de mucha depuración frustrante, creo que descubrí un error en las transacciones de Jet. Después de todo eso, no tiene nada que ver con mi código "enormemente intrincado" o "variables globales malvadas" :)

Parece que cuando lo siguiente es verdadero, obtiene el error # 3034:

  • Abre un conjunto de registros tipo snapshot
  • El conjunto de registros se abre antes de iniciar la transacción
  • El conjunto de registros se cierra / desreferencia después de comenzar la transacción, pero antes de la confirmación o reversión.

No he comprobado si esto ya se conoce, aunque no puedo imaginar que no lo sea.

Por supuesto, es un poco raro hacer las cosas en este orden de todos modos y pedir problemas, no sé por qué lo hice. Moví abrir / cerrar el conjunto de registros de instantáneas dentro de la transacción y todo funciona bien.

El siguiente código muestra el error:

Public Sub run() Dim db As DAO.Database, qdf As DAO.QueryDef, rst As DAO.Recordset Dim wrk As DAO.Workspace, isInTrans As Boolean On Error GoTo Err_ Set wrk = DBEngine(0) Set db = wrk(0) Set rst = db.OpenRecordset("Table2", DAO.dbOpenSnapshot) wrk.BeginTrans isInTrans = True Set qdf = db.CreateQueryDef("", "INSERT INTO [Table1] (Field1, Field2) VALUES (""Blow"", ""Laugh"");") qdf.Execute dbFailOnError Exit_: Set rst = Nothing Set qdf = Nothing Set db = Nothing If isInTrans Then wrk.CommitTrans isInTrans = False Exit Sub Err_: MsgBox Err.Description If isInTrans Then wrk.Rollback isInTrans = False Resume Exit_ End Sub

Y esto soluciona el error:

Public Sub run() Dim db As DAO.Database, qdf As DAO.QueryDef, rst As DAO.Recordset Dim wrk As DAO.Workspace, isInTrans As Boolean On Error GoTo Err_ Set wrk = DBEngine(0) Set db = wrk(0) wrk.BeginTrans isInTrans = True '' NOTE THIS LINE MOVED WITHIN THE TRANSACTION Set rst = db.OpenRecordset("Table2", DAO.dbOpenSnapshot) Set qdf = db.CreateQueryDef("", "INSERT INTO [Table1] (Field1, Field2) VALUES (""Blow"", ""Laugh"");") qdf.Execute dbFailOnError Exit_: Set rst = Nothing Set qdf = Nothing Set db = Nothing If isInTrans Then wrk.CommitTrans isInTrans = False Exit Sub Err_: MsgBox Err.Description If isInTrans Then wrk.Rollback isInTrans = False Resume Exit_ End Sub


Por lo que vale, esto parece estar un poco más extendido que las transacciones de acceso. Acabo de encontrar una situación similar usando Access 2007 y DAO como interfaz para MySQL. Con MySQL Autocommit=0 , las transacciones de SQL sin embargo se comprometerían misteriosamente a la mitad de una transacción.

Después de 2 semanas de rascarse la cabeza, me encontré con esta publicación y volví a mirar mi código. Efectivamente, las inserciones de MySQL se realizaban mediante un procedimiento almacenado que se llamaba desde un módulo de clase de VBA. Este módulo de clase tenía un dao.recordset que se abrió en module.initialize() y se cerró en terminate() . Además, este conjunto de registros se utilizó para recopilar los resultados del procedimiento almacenado. Así que tuve (en pseudo código ...)

module.initialize - rs.open class properties set by external functions transaction.begins Mysql procedure.calls using class properties as parameters - commit(or rollback) rs.populate class properties.set properties used by external functions module terminate - rs.close

y las transacciones simplemente no funcionaban. Intenté todo lo imaginable durante 2 semanas. Una vez que declare y cerré el rs dentro de la transacción, ¡todo funcionó perfectamente!