Desde Access 2010, Access admite el tipo de datos adjuntos que, en apariencia, parece una característica conveniente para almacenar imágenes o archivos pequeños. Sin embargo, una búsqueda rápida en Google generalmente mostrará que es mejor evitarlos. Todo esto se reduce al hecho de que un tipo de datos adjuntos es en realidad un campo de varios valores (MVF), y estos vienen con varios problemas. Por un lado, no podrá usar consultas para insertar o actualizar varios registros de una sola vez. De hecho, cualquier tabla que contenga ese tipo de datos lo obliga a hacer una gran cantidad de código y solo por esa razón, evitamos usar dichos tipos de datos normalmente.
Sin embargo, hay un problema. Nos encanta usar la galería de imágenes y los temas, los cuales dependen de una tabla del sistema, MSysResources
que desafortunadamente usa los tipos de datos adjuntos. Esto ha creado un problema para administrar recursos en nuestra biblioteca estándar porque queremos usar MSysResources
pero no podemos actualizarlos o insertarlos de forma masiva fácilmente.
El tipo de datos adjuntos (así como los MVF) lo obligan a usar la programación "fila por fila agonizante" cuando se trata de un campo MVF, es un twofer con el campo Adjuntos porque tendría que usar el LoadFromFile
o SaveToFile
métodos. Microsoft tiene un artículo con ejemplos sobre esos métodos. Por lo tanto, debe interactuar con el sistema de archivos al agregar nuevos registros. No siempre deseable en todas las situaciones. Ahora, si estamos copiando de una tabla a otra tabla, podemos evitar rebotar sobre el sistema de archivos haciendo algo como:
Dim SourceParentRs As DAO.Recordset2 Dim SourceChildRs As DAO.Recordset2 Dim TargetParentRs As DAO.Recordset2 Dim TargetChildRs As DAO.Recordset2 Dim SourceField As DAO.Field2 Set SourceParentRs = db.OpenRecordset("TableWithAttachmentField", dbOpenDynaset) Set TargetParentRs = db.OpenRecordset("AnotherTableWithAttachmentField", dbOpenDynaset, dbAppendOnly) Do Until SourceParentRs.EOF TargetParentRs.AddNew For Each SourceField In SourceParentRs.Fields If SourceField.Type <> dbAttachment Then TargetParentRs.Fields(SourceField.Name).Value = SourceField.Value End If Next TargetParentRs.Update 'Must save record first before can edit MVF fields TargetParentRs.Bookmark = TargetParentRs.LastModified Set SourceChildRs = SourceParentRs.Fields("Data").Value Set TargetChildRs = TargetParentRs.Fields("Data").Value Do Until SourcechildRs.EOF TargetChildRs.AddNew Const ChunkSize As Long = 32768 Dim TotalSize As Long Dim Offset As Long TotalSize = SourceChildRs.Fields("FileData").FieldSize Offset = TotalSize Mod ChunkSize TargetChildRs.Fields("FileData").AppendChunk(SourceChildRs.GetChunk(0, Offset) Do Until Offset > TotalSize TargetChildRs.Fields("FileData").AppendChunk(SourceChildRs.GetChunk(Offset, ChunkSize) Offset = Offset + ChunkSize Loop TargetChildRs.Update SourceChildRs.MoveNext Loop TargetParentRs.Update SourceParentRs.MoveNext Loop
¡Santo bucle, batman! Eso es mucho código, todo solo para copiar archivos adjuntos de una tabla a otra. Aunque no rebotamos sobre el sistema de archivos, también es muy lento. Según nuestra experiencia, una tabla con 1000 registros que contienen un solo archivo adjunto puede tardar minutos solo para procesar. Ahora, esto es bastante grande si se considera el tamaño. La mesa con los accesorios no es tan grande. De hecho, hagamos un experimento. Veamos qué sucede si copio y pego a través de la hoja de datos:
Así que copiar y pegar es prácticamente instantáneo. Obviamente el código que se usa al pegar no es el mismo código que usaríamos en VBA. Sin embargo, creemos firmemente que si podemos hacerlo de forma interactiva, también podemos hacerlo en VBA. ¿Podemos replicar la velocidad del pegado interactivo en VBA? La respuesta resulta ser sí, ¡podemos!
Acelera con…. XML?
Sorprendentemente, el método que proporciona la forma más rápida de copiar datos, incluidos los archivos adjuntos, es a través de archivos XML. Admito que no busco archivos XML, excepto como una solución a las limitaciones. En promedio, los archivos XML son relativamente lentos en comparación con otros formatos de archivo, pero en este caso, XML tiene una gran ventaja; no tiene problemas para describir los MVF. Creemos un archivo XML e investiguemos las capacidades que obtenemos al importar/exportar un archivo XML.
Después del diálogo habitual del asistente de exportación para establecer la ruta para guardar el archivo XML, obtendremos un diálogo como este:
Si luego hacemos clic en el botón "Más opciones...", obtendremos este cuadro de diálogo en su lugar:
De este diálogo, vemos algunas pistas más sobre lo que es posible; a saber:
- Tenemos la opción de exportar toda la tabla o solo un subconjunto de la tabla aplicando un filtro
- Podemos transformar la salida XML.
- Podemos describir el esquema además del contenido de la tabla.
Encuentro que es mejor incrustar el esquema; el valor predeterminado es exportarlo, pero como un archivo separado. Sin embargo, eso puede ser propenso a errores y pueden olvidarse de incluir el archivo XSD con el archivo XML. Esto se puede cambiar a través de la pestaña de esquema que se muestra:
Terminemos de exportarlo y echemos un vistazo rápido a los datos del archivo XML resultante.
Tenga en cuenta que los archivos adjuntos se describen dentro de los Data
el subárbol y el contenido del archivo están codificados en base 64. Intentemos importar el archivo XML. Después de pasar por el asistente de importación, obtendremos este cuadro de diálogo:
Tome nota de las siguientes características:
- Al igual que con la exportación, tenemos la opción de transformar el XML.
- Podemos controlar si importamos la estructura, los datos o ambos
Si luego terminamos de importar el archivo XML, encontramos que es tan rápido como la operación de copiar y pegar que hicimos.
Ahora sabemos que hay un camino mejor para copiar varios registros con archivos adjuntos. Pero en esta situación, queremos hacer esto de forma programática, en lugar de interactiva. ¿Podemos hacer lo mismo que acabamos de hacer? De nuevo, la respuesta es sí. Hay varias formas de hacer lo mismo, pero creo que el método más fácil es usar los 3 nuevos métodos que se agregaron a la Application
objeto desde Access 2010:
ExportXML
métodoTransformXML
métodoImportXML
método
Tenga en cuenta que el ExportXML
El método admite la exportación desde varios objetos. Sin embargo, debido a que el objetivo aquí es poder copiar o actualizar en masa los registros de una tabla con campos adjuntos, el mejor tipo de objeto que podemos usar es una consulta guardada. Con una consulta guardada, podemos controlar qué filas se deben insertar o actualizar y también podemos dar forma a la salida. Si observa el diseño del esquema de MSysResources
tabla a continuación:
Hay un problema potencial. Cada vez que usamos temas o imágenes, hacemos referencia al elemento por nombre, no por ID. Sin embargo, el Name
columna no es única y no es la clave principal de la tabla. Por lo tanto, cuando agregamos o actualizamos registros, queremos hacer coincidir el Name
columna, no el Id
columna. Esto significa que cuando exportamos, probablemente no deberíamos incluir el Id
columna y deberíamos exportar solo la lista única del Name
para asegurarse de que los recursos no pasen repentinamente de "Open.png" a "Close.png" o algo tonto.
Luego crearemos una consulta para que actúe como la fuente de los registros que queremos importar a MSysResources
mesa. Comencemos con este SQL solo para demostrar el filtrado a un subconjunto de registros:
SELECT e.Data, e.Extension, e.Name, e.Type FROM Example AS e WHERE e.Name In ("blue","red","green");
Luego lo guardaremos como qryResourcesExport
. Luego podemos escribir código VBA para exportar XML:
Application.ExportXML _ ObjectType:=acExportQuery, _ DataSource:="qryResourcesExport", _ DataTarget:="C:\Path\to\Resources.xml", _ OtherFlags:=acEmbedSchema
Esto emula la exportación que hicimos originalmente de forma interactiva.
Sin embargo, si luego importamos el XML resultante, solo tenemos la opción de agregar datos a una tabla existente. No podemos controlar a qué tabla se agregará; encontrará una tabla o tabla de consulta con el mismo nombre (por ejemplo, qryResourcesExport
y anexar registros a esa consulta. Si la consulta es actualizable, entonces no hay problema y se insertará en el Example
en el que se basa la consulta. Pero, ¿qué pasa si la consulta de origen que usamos no es actualizable o puede que no exista? En cualquiera de los casos, no podríamos importar el archivo XML tal como está. Podría fallar al importar o terminar creando una nueva tabla llamada qryResourcesExport
que no nos ayuda. Y qué pasa con el caso de copiar datos de Example
a MSysResources
? No queremos agregar datos al Example
mesa.
Ahí es donde el TransformXML
método viene al rescate. Una discusión completa sobre cómo escribir una transformación XML está más allá del alcance, pero debería poder encontrar amplios recursos sobre cómo escribir una hoja de estilo XSLT para describir la transformación. También hay varias herramientas en línea que puede usar para validar su XSLT. Aquí hay uno. Para el caso simple en el que solo queremos controlar en qué tabla el archivo XML debe agregar los registros, puede comenzar con este archivo XSLT. A continuación, puede ejecutar el siguiente código VBA:
Application.TransformXML _ DataSource:="C:\Path\to\Resources.xml", _ TransformSource:="C:\Path\to\ResourcesTransform.xslt", _ OutputTarget:="C:\Path\to\Resources.xml", _ WellFormedXMLOutput:=True, _ ScriptOption:=acEnableScript
Podemos reemplazar el archivo XML original con el archivo XML transformado, que ahora se insertará en MSysResources
tabla en lugar de en (posiblemente consulta/tabla inexistente) qryResourcesExport
.
Entonces tenemos que manejar las actualizaciones. Porque en realidad estamos agregando nuevos registros y MSysResources
table no tiene ninguna restricción sobre los nombres duplicados, debemos asegurarnos de que todos los registros existentes con los mismos nombres se eliminen primero. Esto se puede lograr escribiendo una consulta equivalente como esta:
DELETE FROM MSysResources AS r WHERE r.Name In ("blue","red","green");
luego ejecútelo primero antes de ejecutar el código VBA:
Application.ImportXML DataSource:="C:\Path\to\Resources.xml", ImportOptions:=acAppendData
Debido a que el archivo XML se transformó, ImportXML
El método ahora insertará los datos en MSysResources
tabla en lugar de la consulta original que usamos con ExportXML
método. Especificamos que debe agregar datos a una tabla existente. Sin embargo, si la tabla no existe, se creará.
Y con eso, hemos logrado una actualización/inserción masiva de la tabla con un campo adjunto que es mucho más rápido en comparación con el código VBA original de conjunto de registros y conjunto de registros secundario. ¡Espero que ayude! Además, si necesita ayuda para desarrollar aplicaciones de Access, ¡no dude en contactarnos!