Why does adding attachments to access DB via script generate errors - ms-access

I'm writing a script that brings emails from an O365 mailbox into MS Access.
Accessing the emails, and exporting the attachments all works fine.
Records can be created in the DB with sender, date/time, subject, body etc.
However, I am unable to add the attachments into the DB's attachment field.
This line always generated a type mismatch error:
Set rsAttach = rstDocs.Fields("Attachments").Value
rstDocs already was used to update the other fields in the record - no issue.
But I need the rsAttach object to load the file attachment.
Already tried numerous variations of creating and defining the rsAttach object. I Tried the online examples and I'm basically as per the book from MSDN and other examples from this web site.
omEmail is the email object from Outlook. Its already saved in TmpPath when we get to here.
Set cn = CreateObject("ADODB.Connection")
Set rstDocs = CreateObject("ADODB.Recordset")
Set rsAttach= CreateObject("ADODB.Recordset")
cn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source='C:\data\CustService.accdb';Persist Security Info=False;"
cn.Open
rstDocs.Open "Documents;", cn, adOpenKeyset, adLockPessimistic, adCmdTable
rstDocs.AddNew
rstDocs("Sender").Value = omEmail.SenderEmailAddress
For Each Attachment In omEmail.Attachments
Set rsAttach = rstDocs.Fields("Attachments").Value ' ERROR HERE
rsAttach.AddNew
rsAttach.Fields("FileData").LoadFromFile TmpPath + Attachment.FileName
rsAttach.Update
Next
rstDocs.Update
rstDocs.Close

rstDocs.Fields("Attachments").Value returns an ADO Field object, not a Recordset like you seem to be expecting, so you can't add a record to that field (rsAttach.AddNew).
If you want to store attachments as individual records, you can create a separate Attachments table in which you have an ID field that is the same as the ID of the e-mail record in rstDocs. Please take a look at this very similar question: Handling fields of Attachment type in MS Access using ADO
Documents table:
EmailID as Number
Sender as Text
Subject as Text
Date as Date/Time
...
Attachments table:
EmailID as Number
FileData as Number
Also, the ADO Field object does not have a LoadFromFile method. You will have do load the data and set it to the Field object's Value property.

Related

Assistance listing query names from many (~100) ms access databases

I have inherited responsibility for the management of our org's client management system. Data is in ms-sql, data entry is done via .net custom screens, and reporting is done via ms-access.
My predecessor (who never had much tech training) responded to user requests for data by building queries in ms-access, exporting the data and sending the data as an email attachment. What with various attempts to organize things over the years, .mdb's corrupting, and other issues, there are now around 100 .mdb files with his queries in them. Each of the mdbs may have around 100 queries.
Now, users will email me with the attachment he sent them and say "your predecessor created this report for me a few months ago. Can you run it again?" I can see the name of the query because it is the name of the exported excel file. I go and open the .mdb files he used most and look for the query name but am unable to find many of them.
Any ideas of how I could run something against the entire folder directory of .mdb files to get a list of all queries in the .mdb files?
Any ideas are welcome, thanks much.
You can use Dir to loop the filenames:
Public Function ListQueries()
Const Path As String = "C:\Test\"
Dim Workspace As DAO.Workspace
Dim Database As DAO.Database
Dim Query As DAO.QueryDef
Dim Filename As String
Set Workspace = DBEngine(0)
Filename = Dir(Path & "*.mdb", vbNormal)
While Filename <> ""
Debug.Print Filename
Set Database = Workspace.OpenDatabase(Path & Filename, , True)
For Each Query In Database.QueryDefs
If Asc(Query.Name) <> 126 Then
Debug.Print , Query.Name
End If
Next
Database.Close
Filename = Dir
Wend
End Function

Need to Change Linked Tables in MS Access DB with Username, Password and System DB

I have a front end DB, which needs to link to different back end DBs. To give you perspective, it relates to stand alone MDB files. The software in question builds a DB per company.
At the moment I am writing code within one of these MDB files.
For scalability I am now creating a new DB which will link to each MDB via code, and therefore my questions are as follows
How do I change the linked table location via code / VB so that the user can select the company / DB they want to work on
How do I do this with passing a username and password which is the same for all of the companies / DBs
And as per below we need to verify the username and password via the systemDB for it to open successfully.
As an FYI, this is how we open the DB on a standalone basis-
"C:\Program Files (x86)\Microsoft Office\root\Office16\MSACCESS.EXE" "C:\temp\SAMPLE.mdb" /WRKGRP "C:\ProgramData\SOFTWARE\SYSTEM.mdw" /user:username /pwd:password
This is not a problem at all and is absolutely able to be accomplished given the clarification that you are using a single MDW file.
To clarify Microsoft Access Workgroup Security is essentially a "session" security model that applies directly to the front-end MDB file as you open it.
Your example command line means that Microsoft Access will open the SAMPLE.MDB front-end file using the workgroup file you specified.
Once Microsoft Access has opened SAMPLE.MDB under that workgroup file, you cannot change to another workgroup file within that "session" without closing Microsoft Access and re-opening under the new workgroup file.
FYI - it IS possible to open via code, a table in another MDB using another workgroup file within that connection, but in this manner, the table is only usable in code as a RecordSet (for example), you can't make it a linked table.
Anyway, back to your real issue. How to link a different back-end set of tables for each Company.
My recommendation would be to add a few fields to your Company table that defines the filename and location of each back-end file. For example:
Notice that the location can be a UNC path, or a mapped drive path. Or maybe you don't need to define a location in the table explicitly. Maybe all of the back-ends are in the same folder, or in a definable dynamic location like \Dallas\Dallas.mdb, \NewYork\NewYork.mdb, etc. As long as you can determine the location of each back-end in some manner, then you are fine.
Now, since you will likely have "global" front-end tables, maybe also some "global" linked back-end tables i.e. Common.mdb, and your company-specific back-end tables, I would recommend having a front-end table that defines the name of each of the tables that is involved only in the company-specific files. That way, we can easily loop through just those table names and make the link changes.
For the linking code, let's say that you have prompted the User for which Company they want, and you pass the CompanyID to a re-linking function:
Public Function ChangeCompanyLinks(CompanyID As Long) As Boolean
Dim db As DAO.Database
Dim ldb As DAO.Database
Dim tdf As DAO.TableDef
Dim rstCompany As DAO.Recordset
Dim rstTables As DAO.Recordset
Dim mssql As String
Dim dbFullPath As String
Dim retVal As Boolean
Set db = CurrentDb()
retVal = False
mssql = "SELECT * FROM [tblCompany] WHERE [CompanyID] = " & CompanyID
Set rstCompany = db.OpenRecordset(mssql, dbOpenSnapshot)
If Not rstCompany.BOF Then
dbFullPath = rstCompany("DBLocation") & "\" & rstCompany("DBName")
If Dir(dbFullPath) = rstCompany("DBName") Then
'NOTE: By opening a temporary constant link to the back-end during
' relinking, the relinking runs faster
Set ldb = OpenDatabase(dbFullPath)
mssql = "SELECT * FROM [tblLinkedTables] WHERE [FileType] = ""Company"""
Set rstTables = db.OpenRecordset(mssql, dbOpenSnapshot)
Do While Not rstTables.EOF
Set tdf = db.TableDefs(rstTables("TableName"))
tdf.Connect = ";DATABASE=" & dbFullPath
tdf.RefreshLink
rstTables.MoveNext
Loop
rstTables.Close
ldb.Close
retVal = True
Else
MsgBox "Unable to Locate Company File"
End If
End If
rstCompany.Close
ChangeCompanyLinks = retVal
Set rstCompany = Nothing
Set rstTables = Nothing
Set ldb = Nothing
Set tdf = Nothing
db.Close
Set db = Nothing
End Function
Obviously you will want to add error handling, and customize this a bit to fit your situation, but this code will re-link the specified tables to a new back-end.
Please note that if you eventually change to have your back-end tables in SQL Server (which I highly recommend), the re-linking code would need to be modified a bit. See this answer for more details on that.

ACCESS 2010 SQL No data returned. SQL Returns data same query

I have the same SQL query in Access and I checked it out in SQL Server and coming up with two different results. In access I am getting 0 records sent back (Recordcount = 1) but nothing shows. In SQL I get about 30 records. Not sure what is going on.
Dim sql as String
Dim rs As Recordset
sql = "SELECT * FROM Client Where State = 'MN'"
Set rs = CurrentDb.OpenRecordset(sql, dbOpenDynaset, dbSeeChanges)
'Being called from frmClient
DoCmd.OpenForm "frmUpdate2", acNormal
The text fields on frmUpdate2 are coming up blank.
Control Source is correct and matches
Filter Lookup = Database Default
Enabled = Yes
frmUpdate2:
Record Source = Client
Recordset Type = Dynaset
Allow Additions = Yes
There is another form property called "Data Entry" which may be involved. (Find it on the "Data" tab of the form's property sheet.)
That property doesn't mean what many people expect.
With Data Entry = Yes, the form allows you to add new records but existing records are not displayed.
Ensure the property is set as Data Entry = No if you want to both display/edit existing records and add new records.

Set up Access table to perform multiple records at time

I performed some research how do I need to set up my DB but I need your advice how to.
I have few tables in my db ( db is for incoming material ) in this db are below tables:
Material table
incoming delivery
measurements
supplier
time measurement
Let me explain logic of this db.
When delivery come user will input some data in form (creation of incoming list) where he will basically enter all data necessary to start process of receiving. So once he hit button save record he will create record in tables incoming delivery and time measure.
Until this point everything works perfectly. When next user received this incoming list he got some data where was one hyperlink to file where they put it measurements.
And here come my problem.
I want data to be input in Access rather than to excel (form input looks much more better [yes this is most important reason :) ] ).
So for that I created table called measurements, where I plan input [incoming delivery ID], [material id], [primal key] , and that 41 another columns for measurement(this columns need to be separated cause we have many parts and each got different No. of measurement and user will get information via user form ( opening different form based on material id [this works]).
So after describing its logic I am requesting you people how do i create with 1 record to measurement table each time different numbers of measurements in measurements table for it.
put it even more simple just for case. When user hit button to save the record which creates record in delivery list will also create for example additional 5 records (this number will be based on cell value) in measurement table linked with incoming delivery. (relation is of course set up to one-many)
so in the end when i will create somehow continuous table for data input. User will see form where he got incoming delivery No. some information from other tables and as mentioned 41 items to measure 5 times ( 41 columns and 5 rows )
Hope that my explanation is clear and rly need your help i am screwed :D
Hints:
Use VBA to automate the creation of records. Look for information about DAO and/or ADO and how to use them to insert records (I personally use DAO when I work with Access, it works but it's old).
Do your homework. Before asking a question, it is important that you do your research and that you try to solve the problems by yourself. Try to help yourself before asking others. Please read this article.
Maybe this snippet of code can help you. You'll need to call this method from an event (button_clic or something in your form):
public sub addRecords(id as integer)
dim db as dao.database, rsIn as dao.recordset, rsOut as dao.recordset
dim strSQL as String
dim someValue as integer, i as integer ' Test values
' "Connect" to your current database
set db = currentdb
' Create a recordset with the input data you need (read only)
strSQL = "select * from tbl_inputTable where id=" & id
set rsIn = db.openrecordset(strSQL, dbOpenDynaset, dbReadOnly)
' Create a recordset to your output table
set rsOut = db.openRecorset("tbl_outputTable", dbOpenDynaset, dbAppendOnly)
' Read the data from the input table
with rsIn
.moveFirst
someValue = rsIn![aField]
end with
' Write some test data to your output table
with rsOut
for i = 1 to someValue
.addNew
rsOut![fk_id] = id
rsOut![theValue] = i
.update
next i
end with
' Close every recordset and databases (this does not close your application)
rsIn.close
rsOut.close
db.close
end sub
In your input form, write this in the "On Click" event:
sub button1_click()
call addRecords(txtId.value) ' I am assuming that there's a text box called "txtId"
end sub
This is just a sample of what you can do with DAO. I won't (and maybe nobody else would) write the full code for you: You'll need to fit this to your particular problem.

How to retrieve several rows from database

Okay, so I've made a new project which allows users to send messages to each other. I've almost got it finished, just a few things need to be cleared up.
I've successfully allowed the user to send the message (via mysql). Now Im at the part where Im supposed to retrieve the message and display it in the form; this is where Im stuck. It'd be easy to do if the user has only 1 message in the database, but what if he had more than 1? How would I retrieve them all and display them in the form. Im using this query:
SELECT ToID
FROM Message
WHERE (ID LIKE #ID)
What it does is check the database for any messages containing the recipient as the user's ID. If the database does in fact contain any message, then the program will display them in the textboxes. But in the case if more than 1 message, how would the database display them in the form? The form has textboxes for:
Sender
Subject
Date.
This is the query I hoped to use for retrieving the message:
SELECT ID, Title, Body, Date, FromUsername
FROM Message
WHERE (ID LIKE #ID)
Please help? Any other solutions are fine as long as its easy to understand(im new to vb).
If I understand you correctly, why not use a list box instead of a textbox to display the messages. That way it doesn't matter how many messages there are, they will just keep getting added to listbox.
Something like:
For Each s as String in messageList
listbox1.items.add(s)
Next
Edit:
Retrieve the data and then bind it to the listbox - I've never really worked with mysql so I've lifted the code from a google search - it should get you started though.
dim table as new DataTable("table1")
' Create a Connection
using conn as new MysqlConnection("...connectionstring")
conn.Open() ' Open it
' Create a new Command Object
using cmd as new MysqlCommand("SELECT ID, Title, Body, Date, FromUsername FROM Message WHERE (ID LIKE #ID)", conn)
' Create a DataAdapter
' A DataAdapter can fill a DataSet or DataTable
' and if you use it with a CommandBuilder it also
' can persist the changes back to the DB with da.Update(...)
using da as new MysqlDataAdapter(cmd)
da.Fill(table) ' Fill the table
end using
end using
end using
' A Binding Source allows record navigation
dim bs as new BindingSource(table, nothing)
' You can bind virtually every property (most common are "text" "checked" or "visible"
' of a windows.forms control to a DataSource
' like a DataTable or even plain objects
ListBox1.DisplayMember = "Message"
ListBox1.ValueMember = "ID"
ListBox1.DataSource = table2