I have a table in an access table which contains Product entries, one of the columns has a jpg image stored as an OLE Object. I am trying to import this table to MySQL but nothing seems to work. I have tried the MySQL migration tool but that has a known issue with Access and OLE Objects. (The issue being it doesnt work and leaves the fields blank) I also tried the suggestion on this site
and while the data is imported it seems as though the image is getting corrupted in the transfer. When i try to preview the image i just get a binary view, if i save it on disk as a jpg image and try to open it i get an error stating the image is corrupt.
The images in Access are fine and can be previewed. Access is storing the data as an OLE Object and when i import it to MySql it is saved in a MediumBlob field.
Has anyone had this issue before and how did they resolve it ?
Ok so in the interests of airing my dirty code in public here what i came up with.
Note : this is a hack designed to be used once and then thrown away.
This Method takes in a datarowview containing 1 row of data from the access table. The Images are wrapped in OLE serialization, im not entirely familiar with how this works but its how Microsoft apps allow any object to be embedded into something else. (eg images into Excel Cells). I needed to remove the serialization junk around the image so i loaded the entire field as a Byte array and searched through it for 3 concurrent entries (FF D8 FF) which represent the beginning of the image data within the field.
Private Function GetImageFromRow(ByRef row As DataRowView, ByVal columnName As String) As Bitmap
Dim oImage As Bitmap = New Bitmap("c:\default.jpg")
Try
If Not IsDBNull(row(columnName)) Then
If row(columnName) IsNot Nothing Then
Dim mStream As New System.IO.MemoryStream(CType(row(columnName), Byte()))
If mStream.Length > 0 Then
Dim b(Convert.ToInt32(mStream.Length - 1)) As Byte
mStream.Read(b, 0, Convert.ToInt32(mStream.Length - 1))
Dim position As Integer = 0
For index As Integer = 0 To b.Length - 3
If b(index) = &HFF And b(index + 1) = &HD8 And b(index + 2) = &HFF Then
position = index
Exit For
End If
Next
If position > 0 Then
Dim jpgStream As New System.IO.MemoryStream(b, position, b.Length - position)
oImage = New Bitmap(jpgStream)
End If
End If
End If
End If
Catch ex As Exception
Throw New ApplicationException(ex.Message, ex)
End Try
Return oImage
End Function
Then its a matter of pulling out this data into a bitmap. So for each row in the access table i extract the bitmap and then update the corresponding MySQL entry.
It worked fine but im guessing i could have removed the serialisation stuff in a better way, perhaps theres an API to do it.
As far as I remember, the Microsoft "SQL Server Migration Assistant for Access" will properly migrate OLE Images, but this is only for Access->SQLServer. However, what you can do is use this to migrate to SQLServer Express (free download) and then migrate from SQLServer to MySQL.
There's also olefield - Python module to extract data out of OLE object fields in Access. I successfully extracted BMP files with it. It could probably work with jpeg images, but I haven't tried it.
Related
I have an Access table where each item has attached a Visio file (.vsd).
In my Access form, I would like to see the file. I don't care if it is an editable Visio file, a preview or just an image.
I have built a VBA code that let me load the Visio file from a Directory. But I need to load the file from a table.
Here my VBA code.
Private Sub Carica_Dati()
Dim path As String
path = "C:\Users\VisioFlow_001.vsd"
With Me.VisioObject ' name of the OLE Object where I want to put the Visio file
.Class = "Visio.Drawing.11"
.OLETypeAllowed = acOLELinked
.SourceDoc = path ' HERE I WANT TO LOAD THE FILE FROM A TABLE OF THE DB
.Enabled = True
.Locked = False
.Action = acOLECreateLink
.SizeMode = acOLESizeZoom
End With
End Sub
Here a preview of the form.
UPDATE
Here a picture to show how the file is attached to the table.
Since attachment fields in Access aren't very consistent, directly loading them into an OLE object is not an option, unless you're willing to do sophisticated things
Microsofts documentation on attachments can be found here
My observations on attachments: the binary data field contains one of the following:
Some characters I can't identify + the file type + the file data appended to it
Some characters I can't identify + the file type + a compressed version of the file data appended to it
Microsoft, in all it's wisdom, has supplied us with a way to save the original file to the disk, but hasn't supplied us with a way to remove those initial characters and the file type from the actual file data, or an easy way to identify if the file is compressed or not (you can check the file type with the table supplied in the link to check if it should be).
In conclusion, you're probably off best either replacing your attachment field with an OLE object in the database, or writing the attachment files to disk before displaying them.
If you use an OLE object field, and load them in as long binary data (not through the GUI), you can easily achieve the behaviour you seek without writing the file to disk, since the binary data is available without any extra characters.
To write an attachment file to disk:
Dim rsForm As DAO.Recordset2
Dim rsFiles As DAO.Recordset2
Set rsForm = Me.Recordset
Set rsFiles = rsForm.Fields("attachment_column").Value
If Not rsFiles.EOF Then
Dim fileLocation As String
fileLocation = Environ("TEMP") & rsFiles.Fields("FileName").Value
rsFiles.Fields("FileData").SaveToFile fileLocation
'Your existing code to display the OLE object here
End If
You do not want to use the Attachment feature. Its purpose is different than what you are attempting.
Put the images into their own stand alone folder outside of the database.
In the table that holds the records for your main form - you need a new field which holds the path & image file name. This is a text field. (If the path segment is uniform for all one can insert that elsewhere via code rather than store it in this field.)
Then in form design - use the image control. This control (all controls) have a source property - that will change with each record using that field that holds the path & file name.
Do a bing/google on the topic of changing an image with every record - the set up isn't intuitive necessarily. Note that older editions did things differently so be sure you get relatively recent advice.
Then when you are using the form and change records - the image will change.
Note after having typed all this.... I have no idea if the visio file type works - I know that jpg and bmp do... so first sanity check a simple fixed image with that file type to see if it works ...
I need to create copies of the CurrentDB using VBA (approx. 12 copies). The copies need to be clones of the master database containing all the same forms, queries, etc. except only a limited dataset.
DoCmd.CopyDatabaseFile seems to be made for this, but only works if you are using it to copy the DB to an MS SQL Server. As MS states on their website:
Copies the database connected to the current project to a Microsoft
SQL Server database file for export.
docmd.TransferDatabase only exports the data itself, but not the structure, forms, etc.
Code I have found on the web and adapted doesn't work and throws an error on the .CopyFile line saying:
Run-time error 52: Bad file name or number
This is the code
Sub CopyDB()
Dim external_db As Object
Dim sDBsource As String
Dim sDBdest As String
sDBsource = "\\group\bsc\groups\L\BDTP\Compliance\ComplianceData\Compliance Group Reporting.accdb"
sDBdest = "\\group\bsc\groups\L\BDTP\Compliance\ComplianceData\Compliance Group Reporting_2.accdb"""
Set external_db = CreateObject("Scripting.FileSystemObject")
external_db.CopyFile sDBsource, sDBdest, True
Set external_db = Nothing
End Sub
How can I fix the above line? Alternatively is there a direct command in Access to create a copy? The "create backup" function would be tailor made for this, but I can not find it in VBA.
Looks like you have an extra quote in sDBdest accdb"""
And for database copy you can also use
FileCopy sDBsource, sDBdest
Instead of Scripting object
I have a table with a field that is of type OLE Object. I then have a form with a number of fields. Some are just text fields. But, I also have an Image object on the form. When a user clicks a button, a dialog opens and they can pic a photo. Once they select the photo, the Image object displays that picture. I want to save that image to database. What I have only seems to save a reference to where the file is located, on the computer. So, this would not work if the db is moved. Here is the code I have:
Dim wrkCurrent As DAO.Workspace
Dim dbs As DAO.Database
Dim rstPerson As DAO.Recordset
Set wrkCurrent = DBEngine.Workspaces(0)
Set dbs = CurrentDb
Set rstPerson = dbs.OpenRecordset("SELECT * FROM tbl_person WHERE id =" & ID)
With rstPerson
.Edit
!bio_photo = Me.Image37.picture
.Update
End With
How would I save the image file to the OLE Field, so that I could load it back to the Image object, on the form, at a later time?
Thanks
Answer:
So, what I ended up doing was following this https://support.microsoft.com/en-us/kb/210486 [EDIT: that link is now dead, try this instead]
I use the readBLOB function to read the file and save it into the database. Then, when I run a report or open a form that has the picture, onload, I use the WriteBlob function to write the file to a temp folder and then use that path to populate an Image object.
Databases can grow to enormous sizes when you start embedding images, leading to slower queries, crashes, timeouts, and dissatisfied users. Use OLE with caution when adding images into a database
Simple Google search has how to load ole object from folder into table
And a search on this site comes up with auto attach images using vba
I am running a SSIS package to load say a million rows from a flat file, which uses a script task for complex transformations and a SQL Server table destination. I am trying to figure out the best way (well, ANY way at this stage) to write out to a different table the row count (probably in multiples of 1000 to be more efficient) DURING the data flow processing. This is so that I can determine the percentage of progress throughout a task that might take a few minutes, simply by querying the table periodically.
I can't seem to add any SQL task into the flow, so I'm guessing the only way is to connect to the SQL database inside the .NET script. This seems painful and I'm not even sure it is possible. Is there another more elegant way? I've seen reference to "Rows Read" performance counter but not sure where I access this in SSIS and still not sure how to write it to a SQL table during the Data Flow processing.
Any suggestions appreciated.
Glenn
there are two easy options here:
Option 1: use the built-in logging with SSIS and watch the on progress event. this can be configured to log to several different outputs including relational database and flat files
See more Here
Option 2: you could add a SSIS script component that could fire off notifications to an external system like a database table
I recently solved this in a slightly different manner, which I find superior to using scripting and opening separate connections in code to DBs:
In the source query or a transform shape, add a row count (incremental)
In a conditional branching, use a modulo expression (%) to branch whenever the number is a multiple of for example 1000, but this could be configurable or based on source data (for example 0.0% to 100.0% of the data)
Create a log connection manager and use a destination. Control the batching sizes so that rows are immediately committed to the target table.
Why not write a .NET application and you can integrate into that to get information as to where the SSIS package is at.
Basically everything that is sent to the console you can get, and there are event handlers you can attach to to get information while it is processing the package.
Here is a link that may help you to go with this approach:
http://www.programminghelp.com/database/sqlserver/sql-server-integration-services-calling-ssis-package-in-c/
OK, had some success at last.... added a call to the following sub in the script component:
Sub UpdateLoadLog(ByVal Load_ID As Int32, ByVal Row_Count As Int32, ByVal Row_Percent As Int32, ByVal connstr As String)
Dim dbconn As OleDbConnection
Dim Sql As String
Dim dbcomm As OleDbCommand
dbconn = New OleDbConnection(connstr)
dbconn.Open()
Sql = "update myTable set rows_processed = " & Row_Count & ", rows_processed_percent = " & Row_Percent & " where load_id = " & Load_ID & " and load_log_type = 'SSIS'"
dbcomm = New OleDbCommand(Sql, dbconn)
dbcomm.ExecuteNonQuery()
dbconn.Close()
dbconn = Nothing
dbcomm = Nothing
End Sub
This gets executed every 1000 rows, and successfully updates the table. The row already existed as it gets created in the control flow at the start of the package, and updated again in the control flow at the very end with final rowcount and 100%.
Thanks for all your suggestions guys.
Is the application consuming the row count a .net application? When it comes to sharing information between applications there are a lot of accepted practices. May be you should take a look in to them. And for your particular case, if it is .net application that consumes this row number for calculating progress, may be you can store the information some place else other than a DB table, like file system, web service, windows environment variables, log (like windows events log), etc are some that came to my mind now. I think updating a windows environment variable with row count form with in your script component will be a good enough solution. Just like using a global variable to share data between two functions inside a program. :)
Does anyone know how to modify an existing import specification in Microsoft Access 2007 or 2010? In older versions there used to be an Advanced button presented during the import wizard that allowed you to select and edit an existing specification. I no longer see this feature but hope that it still exists and has just been moved somewhere else.
I am able to use this feature on my machine using MS Access 2007.
On the Ribbon, select External Data
Select the "Text File" option
This displays the Get External Data Wizard
Specify the location of the file you wish to import
Click OK. This displays the "Import Text Wizard"
On the bottom of this dialog screen is the Advanced button you referenced
Clicking on this button should display the Import Specification screen and allow you to select and modify an existing import spec.
For what its worth, I'm using Access 2007 SP1
I don't believe there is a direct supported way. However, if you are desparate, then under navigation options, select to show system objects. Then in your table list, system tables will appear. Two tables are of interest here: MSysIMEXspecs and MSysIMEXColumns. You'll be able edit import and export information. Good luck!
Tim Lentine's answer seems to be true even in the full release. There is just one other thing I would like to mention.
If you complete your import without going into "Advanced..." and saving the spec, but you do save the import for reuse at the end of the wizard (new feature AFAIK), you will not be able to go back and edit that spec. It is built into the "Saved Import". This may be what Knox was referring to.
You can, however, do a partial work around:
Import a new file (or the same one all over again) but,
This time choose to append, instead of making a new
Click OK.
Go into "advanced" All your column heading and data-types will be there.
Now you can make the changes you need and save the spec inside that dialog. Then cancel out of that import (that is not what you wanted anyway, right?)
You can then use that spec for any further imports. It's not a full solution, but saves some of the work.
Below are three functions you can use to alter and use the MS Access 2010 Import Specification. The third sub changes the name of an existing import specification. The second sub allows you to change any xml text in the import spec. This is useful if you need to change column names, data types, add columns, change the import file location, etc.. In essence anything you want modify for an existing spec. The first Sub is a routine that allows you to call an existing import spec, modify it for a specific file you are attempting to import, importing that file, and then deleting the modified spec, keeping the import spec "template" unaltered and intact. Enjoy.
Public Sub MyExcelTransfer(myTempTable As String, myPath As String)
On Error GoTo ERR_Handler:
Dim mySpec As ImportExportSpecification
Dim myNewSpec As ImportExportSpecification
Dim x As Integer
For x = 0 To CurrentProject.ImportExportSpecifications.Count - 1
If CurrentProject.ImportExportSpecifications.Item(x).Name = "TemporaryImport" Then
CurrentProject.ImportExportSpecifications.Item("TemporaryImport").Delete
x = CurrentProject.ImportExportSpecifications.Count
End If
Next x
Set mySpec = CurrentProject.ImportExportSpecifications.Item(myTempTable)
CurrentProject.ImportExportSpecifications.Add "TemporaryImport", mySpec.XML
Set myNewSpec = CurrentProject.ImportExportSpecifications.Item("TemporaryImport")
myNewSpec.XML = Replace(myNewSpec.XML, "\\MyComputer\ChangeThis", myPath)
myNewSpec.Execute
myNewSpec.Delete
Set mySpec = Nothing
Set myNewSpec = Nothing
exit_ErrHandler:
For x = 0 To CurrentProject.ImportExportSpecifications.Count - 1
If CurrentProject.ImportExportSpecifications.Item(x).Name = "TemporaryImport" Then
CurrentProject.ImportExportSpecifications.Item("TemporaryImport").Delete
x = CurrentProject.ImportExportSpecifications.Count
End If
Next x
Exit Sub
ERR_Handler:
MsgBox Err.Description
Resume exit_ErrHandler
End Sub
Public Sub fixImportSpecs(myTable As String, strFind As String, strRepl As String)
Dim mySpec As ImportExportSpecification
Set mySpec = CurrentProject.ImportExportSpecifications.Item(myTable)
mySpec.XML = Replace(mySpec.XML, strFind, strRepl)
Set mySpec = Nothing
End Sub
Public Sub MyExcelChangeName(OldName As String, NewName As String)
Dim mySpec As ImportExportSpecification
Dim myNewSpec As ImportExportSpecification
Set mySpec = CurrentProject.ImportExportSpecifications.Item(OldName)
CurrentProject.ImportExportSpecifications.Add NewName, mySpec.XML
mySpec.Delete
Set mySpec = Nothing
Set myNewSpec = Nothing
End Sub
When I want to examine or change an import / export specification I query the tables in MS Access where the specification is defined.
SELECT
MSysIMEXSpecs.SpecName,
MSysIMexColumns.*
FROM
MSysIMEXSpecs
LEFT JOIN MSysIMEXColumns
ON MSysIMEXSpecs.SpecID = MSysIMEXColumns.SpecID
WHERE
SpecName = 'MySpecName'
ORDER BY
MSysIMEXSpecs.SpecID, MSysIMEXColumns.Start;
You can also use an UPDATE or INSERT statement to alter existing columns or insert and append new columns to an existing specification. You can create entirely new specifications using this methodology.
Another great option is the free V-Tools addin for Microsoft Access. Among other helpful tools it has a form to edit and save the Import/Export specifications.
Note: As of version 1.83, there is a bug in enumerating the code pages on Windows 10. (Apparently due to a missing/changed API function in Windows 10) The tools still works great, you just need to comment out a few lines of code or step past it in the debug window.
This has been a real life-saver for me in editing a complex import spec for our online orders.
Why so complicated?
Just check System Objects in Access-Options/Current Database/Navigation Options/Show System Objects
Open Table "MSysIMEXSpecs" and change according to your needs - its easy to read...
Tim Lentine's answer works IF you have yours specs saved. Your question did not specify that, it only stated you had imported the data. His method would not save your specs that way.
The way to save the spec of that current import is to re-open the import, hit "apend" and that will allow you to use your current import settings that MS Access picked up. (This is useful if your want to keep the import specs from an Excel format you worked on prior to importing into MS ACCESS.)
Once you're in the apend option, use Tim's instructions, which is using the advanced option and "Save As." From there, simply click cancel, and you can now import any other similar data to various tables, etc.
I have just discovered an apparent bug in the whole Saved Import/XML setup in Access. Also frustrated by the rigidity of the Saved Import system, I created forms and wrote code to pick apart the XML in which the Saved Import specs are stored, to the point that I could use this tool to actually create a Saved Import from scratch via coded examination of a source Excel workbook.
What I've found out is that, while Access correctly imports a worksheet per modifications of default settings by the user (for example, it likes to take any column with a header name ending with "ID" and make it an indexed field in the resulting table, but you can cancel this during the import process), and while it also correctly creates XML in accordance to the user changes, if you then drop the table and use the Saved Import to re-import the worksheet, it ignores the XML import spec and reverts back to using its own invented defaults, at least in the case of the "ID" columns.
You can try this on your own: import an worksheet Excel with at least one column header name ending with "ID" ("OrderID", "User ID", or just plain "ID"). During the process, be sure to set "Indexed" to No for those columns. Execute the import and check "Save import steps" in the final dialog window. If you inspect the resulting table design, you will see there is no index on the field(s) in question. Then delete the table, find the saved import and execute it again. This time, those fields will be set as Indexed in the table design, even though the XML still says no index.
I was pulling my hair out until I discovered what was going on, comparing the XML I built from scratch with examples created through the Access tool.
I used Mike Hansen's solution, it is great. I modified his solution in one point, instead of replacing parts of the string I modified the XML-attribute. Maybe it is too much of an effort when you can modify the string but anyway, here is my solution for that.
This could easily be further modified to change the table etc. too, which is very nice imho.
What was helpful for me was a helper sub to write the XML to a file so I could check the structure and content of it:
Sub writeStringToFile(strPath As String, strText As String)
'#### writes a given string into a given filePath, overwriting a document if it already exists
Dim objStream
Set objStream = CreateObject("ADODB.Stream")
objStream.Charset = "utf-8"
objStream.Open
objStream.WriteText strText
objStream.SaveToFile strPath, 2
End Sub
The XML of an/my ImportExportSpecification for a table with 2 columns looks like this:
<?xml version="1.0"?>
<ImportExportSpecification Path="mypath\mydocument.xlsx" xmlns="urn:www.microsoft.com/office/access/imexspec">
<ImportExcel FirstRowHasNames="true" AppendToTable="myTableName" Range="myExcelWorksheetName">
<Columns PrimaryKey="{Auto}">
<Column Name="Col1" FieldName="SomeFieldName" Indexed="NO" SkipColumn="false" DataType="Double"/>
<Column Name="Col2" FieldName="SomeFieldName" Indexed="NO" SkipColumn="false" DataType="Text"/>
</Columns>
</ImportExcel>
</ImportExportSpecification>
Then I wrote a function to modify the path. I left out error-handling here:
Function modifyDataSourcePath(strNewPath As String, strXMLSpec As String) As String
'#### Changes the path-name of an import-export specification
Dim xDoc As MSXML2.DOMDocument60
Dim childNodes As IXMLDOMNodeList
Dim nodeImExSpec As MSXML2.IXMLDOMNode
Dim childNode As MSXML2.IXMLDOMNode
Dim attributesImExSpec As IXMLDOMNamedNodeMap
Dim attributeImExSpec As IXMLDOMAttribute
Set xDoc = New MSXML2.DOMDocument60
xDoc.async = False: xDoc.validateOnParse = False
xDoc.LoadXML (strXMLSpec)
Set childNodes = xDoc.childNodes
For Each childNode In childNodes
If childNode.nodeName = "ImportExportSpecification" Then
Set nodeImExSpec = childNode
Exit For
End If
Next childNode
Set attributesImExSpec = nodeImExSpec.Attributes
For Each attributeImExSpec In attributesImExSpec
If attributeImExSpec.nodeName = "Path" Then
attributeImExSpec.Value = strNewPath
Exit For
End If
Next attributeImExSpec
modifyDataSourcePath = xDoc.XML
End Function
I use this in Mike's code before the newSpec is executed and instead of the replace statement. Also I write the XML-string into an XML-file in a location relative to the database but that line is optional:
Set myNewSpec = CurrentProject.ImportExportSpecifications.item("TemporaryImport")
myNewSpec.XML = modifyDataSourcePath(myPath, myNewSpec.XML)
Call writeStringToFile(Application.CurrentProject.Path & "\impExpSpec.xml", myNewSpec.XML)
myNewSpec.Execute