Saving OLE Objects in Access field with VBA - sql-server

I know that this subject has been covered to large extend, but I have not been able to find a solution to my particular problem.
I have a table with a column Attachments of OLE Object data type. The back end of the table is SQL Server table with VARBINARY(MAX) data type for Attachments column.
If I right-click on the Attachments field in Access, a menu pops-up with an option to Insert Object... Following this path I could insert a file in the field.
The file inserted this way could be opened for viewing and editing just by double-clicking the field.
Now. I need to do the same using VBA. I need to take the list of files and insert them in the Attachments field in the appropriate rows. This should not be a difficult task as it is widely known how to insert a file in a field using ADODB.Stream. The following is a simple code to try the concept:
Private Sub POC()
Dim db As DAO.Database
Dim rsa As DAO.Recordset
Dim stream As ADODB.stream
Set db = CurrentDb()
Set rsa = db.OpenRecordset("ZipCodeAttachments", dbOpenDynaset, dbSeeChanges)
rsa.MoveFirst
rsa.MoveNext
rsa.Edit
Set stream = New ADODB.stream
stream.Type = adTypeBinary
stream.Open
stream.LoadFromFile Application.CurrentProject.Path & "\Attachments\537.zip"
rsa.Fields("Attachments").value = stream.Read
rsa.Update
rsa.Close
Set rsa = Nothing
Set dba = Nothing
End Sub
The code inserts a file in the Attachments field of the second row. I could validated that value has been added via SSMS. However, when I try to open the field for viewing and editing as I did earlier with the first row, this time I am getting an error:
Clearly, there is something wrong with the way the file is saved with VBA.
What am I doing wrong? How to achieve the same result with VBA as I get with Access user interface?

If you want to store a file as an OLE Package shell object, doing some GUI coding (opening a form with an OLE object, then using that to store the file) is the only way as far as I know.
Create an unbound form called frmLoadOLEObj, with on it a bound OLE object called MyBoundOLEFrame.
On the form, add the following code:
Public Sub SaveOLEObj(rs As DAO.Recordset, fldName As String, FileName As Variant)
'Save the position of the recordset
Dim bkmrk As Variant
bkmrk = rs.Bookmark
'Bind the form to the recordset
Set Me.Recordset = rs
'Move the form to the saved position
Me.Bookmark = bkmrk
'Bind the OLE frame to the field
MyBoundOLEFrame.ControlSource = fldName
MyBoundOLEFrame.Class = "Package"
'Load the attachment into the OLE frame
MyBoundOLEFrame.SourceDoc = FileName
MyBoundOLEFrame.Action = acOLECreateEmbed
End Sub
Then, to add a file to a record:
Dim rsa As DAO.Recordset
Set rsa = CurrentDb.OpenRecordset("ZipCodeAttachments", dbOpenDynaset, dbSeeChanges)
Dim frmOLE As New Form_frmLoadOLEObj
frmOLE.SaveOLEObj rs, "Attachments", Application.CurrentProject.Path & "\Attachments\537.zip"
As you can see, this is very "hacky" code, because it runs GUI operations, and you have code on a form that is not a form, but really a module, but you need a form to put the control on because you can't have the control without the form. I'd rather have a BLOB any day.

Related

Using VBA in MS Word, open a Word document data from inside image column in SQL Server

First I created a table in a SQL Server database with columns like document id, docname and docdata.
Second I managed to upload a document into that table using MS Access and ole object and now I have a word document there, by double click the ole object in the MS Access form interface I could open the document.
What is interesting about this is that I can edit the document and get the changes saved to the document inside the server.
The idea is that I want to use the SQL Server over our LAN-network as an alternative to OneDrive and make collaborative working possible to my coworkers with no internet service.
Now I am trying to open that document using using VBA in MS Word.
Here is my vague attempt:
Option Explicit
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Private Sub CommandButton1_Click()
' my own connection string
cn.Open "provider=sqloledb;server=127.0.0.1,1433;database=accessdb;UID=sa;PWD=111111"
With rs
Set .ActiveConnection = cn
.Source = "select * from docs where did=1"
.LockType = adLockOptimistic
.CursorType = adOpenKeyset
.CursorLocation = adUseClient
.Open
End With
Dim WordObj As Word.Application
'rs![docdata].Verb = -2
'rs![docdata].Action = 7 ' Activates the application.
Dim obdoc As Word.Document
Set obdoc = rs![docdata] ' what property to attach here ??
obdoc.SaveAs2 "D:\thisdoc.doc"
WordObj.Documents.Open obdoc
WordObj.Activate
End Sub
A mismatch error pops up and I did not know how to deal with this, what type of property should retrieve the data from that field and assign it to the newly made document.
Update 20-2-2020
Um, Now I recognized that the Ms-word document isn't a simple RTF file.
It is a zipped folder ! see this !
and in order to verify this I removed the .docx extension and replaced it with .zip,
then I unzipped it and got some folders inside of them a few .xml files..
This means that the upper approach is not compatible with the nature of this file. type.

Change Crystal Reports db ip address in vb.net

I am currently using VB.net for getting the reports in need with Crystal Reports, My access to the SQL database server is using windows authentication , and what i need to do is to be able to change the IP address of the database source , as i am accessing a test server now , and the code will go somewhere else for work.
What i've found:
this is the only way to change the database source
CrReport.SetDatabaseLogon("user", "password", "server", "RJCards")
but then again i am using windows authentication.
P.S: when i am changing the datasource on the report it self its working great, but since we moving the application to another place, it needs to be set manually like to be retrived from a variable or something.
here is a look over my code
Private Sub GetDuplicatedFF()
Dim CrReport As New DuplicatedFF
Dim CrExportOptions As ExportOptions
CrExportOptions = CrReport.ExportOptions
Try
CrReport.SetDatabaseLogon("user", "password", "server", "RJCards")
CrReport.ExportToDisk(ExportFormatType.Excel, My.Settings.defaultDir & "\DuplicatedFF_" & Format(Now.Date, "yyyyMMdd") & ".xls")
'My.Settings.defaultDir & "\DuplicatedFF_" & Format(Now.Date, "yyyyMMdd") & ".xls"
MsgBox("Done Exporting your file")
Catch err As Exception
MessageBox.Show(err.Message)
End Try
End Sub
When you use windows authentication or credentials to access a database through Crystal Reports, that means that the data retrieval SQL statements will be included inside the rpt file. This doesn't give you too much flexibility in scenarios where you need to make changes to IP addresses, database names, etc.
There is a different approach of providing data to the report with the use of a DataSet as a source. You simply add the necessary DataTables with the necessary columns to a DataSet, fill it with data and provide it to the report. So the rpt file won't include any
embedded information (such as SQL statements).
In order to convert an existing report file to use the aforementioned approach, you can use the following steps:
Create an xsd file in your application (Add New Item... → Data → DataSet)
Edit the DataSet and add the necessary DataTable(s) (Right click → Add → DataTable)
Edit the DataTable(s) and add the necessary column(s) (Right click on DataTable → Add → Column)
Edit each column and set its properties such as DataType etc (Left click on column → Change property inside property window)
Now you need to edit the report file and change the Datasource location (Double click on the report → left click on Database Fields in Field Explorer → Set Datasource Location...)
As a Datasource replacement choose ADO.NET (XML) and double click on Make New Connection. Choose the filepath of the xsd file (DataSet) and press finish.
Each DataTable must be paired with an existing Table inside "Current Data source:" by selecting the pair and pressing the Update button.
Now the report file will just have the necessary placeholders (columns) without any database connection or SQL statements. To load data to the report, use the code below (changed according to your needs).
Imports System.Data.SqlClient
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Windows Authentication Connection String'
Dim ConnString As String = "Server=MyServer;Database=MyDb;Trusted_Connection=Yes;"
'The SQL statement to retrieve data'
Dim SqlStatement As String =
<SQL>
SELECT
[column1]
,[column2]
,[column3]
FROM [MyDb].[dbo].[MyTable]
</SQL>
'A new instance to the DataSet that we created'
Dim MyDataSet As New DataSet2
'A new instance to the report file'
Dim MyReport As New CrystalReport1
'A new instance to the SQL adapter'
Dim SqlAdapter As New SqlDataAdapter(SqlStatement, ConnString)
'Fills the DataTable with data retrieved from the database'
SqlAdapter.Fill(MyDataSet.Tables("TestTable1"))
'Sets the DataSet as the report source'
MyReport.SetDataSource(MyDataSet)
'Previews the report'
CrystalReportViewer1.ReportSource = MyReport
End Sub
End Class

How to autopopulate the field of a table in MS Access with a Identity created in seperate table

I am building a form in MS Access using linked ODBC tables which is to become a large input basis for some of our teams. I have most of it worked out however am trying to auto generate a Primary ID for the main table and populate the text box so the agent doesn't have to. Effectively building an auto number in SQL.
I have the generation and rolling edits in the ODBC tables working and am just stuck on getting the generated code into the relevant field. I am using a macro on button click and have the following code:
Private Sub cmdSubmitDetails_Click()
Dim strSQL As String
Dim rst As Recordset
Dim db As Database
Dim para As String
para = InputBox("Please Enter Your User ID:")
strSQL = "SELECT Min([P_ID]) AS ID FROM QA_IDS WHERE EV_ID = " & para & ";"
Set db = CurrentDb
Set rst = db.OpenRecordset(strSQL)
Me.ID.Text = rst!ID
rst.Close
Set rst = Nothing
Set db = Nothing
End Sub
Me.ID is the field I am trying to populate. It is erroring out in the line
Set rst = db.OpenRecordset(strSQL)
with the error Run-time error '3464' Data type mismatch in criteria expression.
It's my first real dabble in MS Access and I've searched for the solution online. Any help would be greatly appreciated.
I managed to achieve what I was trying using a different method and thought I would post for someone to find useful in the future.
To get around the issue of inconsistent datatype I used a query to run the SQL (including inputting the User ID) , and append it to a new table. Then pull the information from that table with a DLookup, then purge that table. I ended up with the following code.
Private Sub cmdSubmitDetails_Click()
Dim x As String
Dim y As String
Dim z As String
DoCmd.SetWarnings False
DoCmd.OpenQuery ("AppendID")
x = DLookup("[ID]", "[TempID]")
Me.ID = x
y = DLookup("[EV_ID]", "[TempID]")
Me.EVALUATOR_ID = y
DoCmd.OpenQuery ("qryDELETE_TEMP_ID")
DoCmd.OpenQuery ("qryAddNextCallID")
DoCmd.OpenQuery ("qryDELETE_MINIMUM_ID")
DoCmd.SetWarnings True
End Sub

Find which form field points to which database fields in MS Access

Let me explain the WEIRDEST client requirement, we're scratching our heads for:
We have an MS Access VBA application with thousands of forms fields in hundreds of forms.
A few fields in these forms populates data from few tables/queries.
A few other fields in forms inserts data to few tables through queries/direct code.
Notice that these tables are linked tables to SQL Server tables.
Is there a way to find which form field is related to which table column in?
Hence, we need some tool/macro to do this.
How do we find which form field points to which database fields in MS Access?
Based on #ClintB's answer, we have prepared the following code. The values in ctl.ControlSource doesn't seems to be referring to actual database objects:
Sub GetFormFieldToDBFieldMapping()
Dim frm As Object
Dim LiveForm As Form
Dim ctl As control
Dim i As Integer
Dim fso As Object
Dim ctlSource As String
Set fso = CreateObject("Scripting.FileSystemObject")
Dim oFile As Object
Set oFile = fso.CreateTextFile("D:\ControlSources.txt")
For Each frm In Application.CurrentProject.AllForms
'To access form controls, open it
DoCmd.OpenForm frm.Name, acViewDesign
Set LiveForm = forms(frm.Name)
For i = 0 To LiveForm.Controls.Count - 1
Set ctl = LiveForm.Controls.Item(i)
If ctl.ControlType = 106 Or ctl.ControlType = 111 Or ctl.ControlType = 110 Or ctl.ControlType = 109 Then
ctlSource = ctlSource & vbCrLf & "Form Name :" & LiveForm.Name & ": Control Name :" & ctl.Name & ": Control Source :" & ctl.ControlSource
End If
Next i
'Do not forget to close when you are done
DoCmd.Close acForm, frm.Name
Next
oFile.WriteLine ctlSource
oFile.Close
Set fso = Nothing
Set oFile = Nothing
End Sub
I Would do something like this. (not actual code)
For each form in db
For each control in form
'Write a record to a table stating which formName, controlName and the control.controlSource
Next
Next
Edit: ControlSource, not RowSource
The code, you've came up with is excellent! This will give you:
Form Name
Control Name
Control Source
The only thing you need is the table name to which the column is coming.
Since, the tables are tables linked to SQL server, you can find all the tables with all their columns.
This will give you:
Table Name
Column Name
Keep both these information in two excel sheets.
Do a V-Lookup on column name to find the table name

Navigate through records with unbounded fields

I am trying to navigate through records (next and prev) in a form that i have created in Microsoft Access 2013. The database is connected to SQL Server 2008. First i have loaded the database for the table using SQL:
Private Sub Form_Load()
Dim strSQL As String
Dim dbs As DAO.Database
Dim Rs As DAO.Recordset
Set dbs = CurrentDb()
strSQL = " SELECT [dbo_tblRank].* " & _
" FROM [dbo_tblRank] "
Set Rs = dbs.OpenRecordset(strSQL, dbOpenDynaset)
Me.rankNo.Value = Rs![rankNo]
Me.rankName.Value = Rs![rankName]
Me.rankDescription.Value = Rs![rankDescription]
Me.noOfRequiredDivings.Value = Rs![noOfRequiredDivings]
End Sub
Now i have created a 'next' button, which i would like to update the following fields to the next values. I have written the code (which doesn't do anything):
Private Sub btnNext_Click()
Me.Recordset.MoveNext
End Sub
What Am i doing wrong?
The issue is that the form is unbound. As a result of this, you've had to write more code in form_open than would have been required were the form/controls bound; Similarly then, in btnNext_Click, you will again have to write more code.
Re-create the recordset, find the current record in the recordset, then move to the next record, and then re-populate your unbound controls from the new record in this new recordset.
Another way would be for you to make you recordset public so that you don't have to reconnect each time.
You will still have to write code to find the current record so that you can move pass it in your btnNext_Click event.
The easy way would be to bound your Form...

Resources