No es porque no tenga DSN, sino porque lo creaste a través de VBA. Si vincula la vista a través de la GUI de Access, le pedirá la clave principal.
Pero a través de VBA, no conoce la clave principal, por lo que la vista vinculada no se puede actualizar. Con una tabla, Access obtiene la clave principal automáticamente a través de ODBC, por lo que la tabla funciona.
S = "CREATE INDEX PrimaryKey ON MyViewName (MyPrimaryKeyField) WITH PRIMARY"
DB.Execute S
Si tiene muchas vistas y las vuelve a vincular regularmente (por ejemplo, al pasar de la base de datos de desarrollo a la de producción), no resulta práctico codificar sus nombres y PK. Escribí una función para recuperar todos los índices de clave principal de las vistas vinculadas y volver a crearlos después de la vinculación.
Si quieres, puedo desenterrarla.
Editar:
Esto es lo que hago:
' This function returns the full DSN-less connect string
Private Function ODBC_String() As String
' In the real world there are several constants and variable in there
ODBC_String = "ODBC;DRIVER={SQL Server};SERVER=aaa;DATABASE=bbb;UID=ccc;PWD=ccc;LANGUAGE=us_english;TRUSTED_CONNECTION=No"
End Function
Para vincular una tabla o ver la primera vez , uso esto (strTable es el nombre de la tabla/vista):
DoCmd.TransferDatabase acLink, "ODBC", ODBC_String(), acTable, strTable, strTable, False, True
Para las tablas, la clave principal (PK) se determina automáticamente. Para una vista, obtengo la ventana de diálogo de Acceso para especificar el PK, igual que si vinculara la vista manualmente.
La información de PK se almacena en el objeto TableDef para la vista vinculada, por lo que nunca tengo que codificarla en ningún lugar .
Para almacenar la información de PK para todas las vistas vinculadas, tengo esta tabla (es una tabla local en la interfaz de Access para simplificar):
t_LinkedViewPK
ViewName Text(100)
IndexFields Text(255)
y esta función. Todas las vistas (y solo Views) se llaman "v_*", por lo que puedo enumerarlos por nombre.
En realidad, no estoy seguro de si puede determinar a partir de un objeto TableDef si apunta a una tabla o a una vista.
Private Sub StoreViewPKs()
Dim TD As TableDef
Dim idx As index
Dim FD As Field
Dim RS As Recordset
Dim S As String
' DB is a global Database object, set to CurrentDB
DB.Execute "Delete * From t_LinkedViewPK"
Set RS = DB.OpenRecordset("t_LinkedViewPK")
For Each TD In DB.TableDefs
If TD.Name Like "v_*" Then
' Views must have exactly one index. If not: panic!
If TD.Indexes.Count <> 1 Then
MsgBox "View " & TD.Name & " has " & TD.Indexes.Count & " Indizes.", vbCritical
Stop
End If
Set idx = TD.Indexes(0)
' Build field list (the index may contain multiple fields)
S = ""
For Each FD In idx.Fields
If S <> "" Then S = S & ", "
S = S & FD.Name
Next FD
RS.AddNew
RS!ViewName = TD.Name
RS!IndexFields = S
RS.Update
End If
Next TD
RS.Close
End Sub
Cuando realizo cambios en las estructuras de tablas o vistas, o cambio la base de datos de origen (esto se hace cambiando la salida de ODBC_String()
), llamo a esta función:
Public Function Sql_RefreshTables()
Dim TD As TableDef
Dim S As String
Dim IdxFlds As String
DB.TableDefs.Refresh
' save current Indizes for Views (recreated after .RefreshLink)
Call StoreViewPKs
For Each TD In DB.TableDefs
If Len(TD.Connect) > 0 Then
If Left(TD.Connect, 5) = "ODBC;" Then
Debug.Print "Updating " & TD.Name
TD.Connect = ODBC_String()
TD.RefreshLink
' View?
If TD.Name Like "v_*" Then
IdxFlds = Nz(DLookup("IndexFields", "t_LinkedViewPK", "ViewName = '" & TD.Name & "'"))
If IdxFlds = "" Then Stop
' Create PK
S = "CREATE INDEX PrimaryKey ON " & TD.Name & " (" & IdxFlds & ") WITH PRIMARY"
DB.Execute S
End If
End If
End If
Next TD
DB.TableDefs.Refresh
End Function
En lugar de la tabla t_LinkedViewPK
, se podría utilizar un objeto de diccionario. Pero mientras desarrollaba esto, fue muy útil tenerlo como una tabla real.