I am doing a small system using Ms. Access (the database has more than 10 tables ) connecting to visual studio. I made a public class for opening the connection to the database so I can use it in every form. Everything is working and I can get the data from the database But any inserting or deleting data in forms, the database in ms access not getting the update. I can see the new records in forms but nothing in the database.
Imports System.Data.OleDb
Public Class dbconnectClass1
'create db connection
Private DBcon As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0; Data Source=dental_clinic.accdb;")
'prepare db command
Private dbcmd As OleDbCommand
' db data
Public DBDA As OleDbDataAdapter
Public DBDT As DataTable
'query parameters
Public params As New List(Of OleDbParameter)
'query statics
Public recordcount As Integer
Public Exception As String
Public Sub ExecQuery(query As String)
'reset query status
recordcount = 0
Exception = ""
Try
'open connection
DBcon.Open()
'create db command
dbcmd = New OleDbCommand(query, DBcon)
'load params into dbcommand
params.ForEach(Sub(p) dbcmd.Parameters.Add(p))
'clear params list
params.Clear()
'excute command and fill dataset
DBDT = New DataTable
DBDA = New OleDbDataAdapter(dbcmd)
recordcount = DBDA.Fill(DBDT)
Catch ex As Exception
Exception = ex.Message
End Try
'close the database connection
If DBcon.State = ConnectionState.Open Then DBcon.Close()
End Sub
'include query and command parameters
Public Sub addparam(name As String, value As Object)
Dim newparam As New OleDbParameter(name, value)
params.Add(newparam)
End Sub
End Class
This my code inside the forms:
Public Class NewExpense
Private access As New dbconnectClass1
' a varuble having the appointment Id to connect between 2 forms
Private appointmentNo As Integer
Private Function NoError(Optional report As Boolean = False) As Boolean
If Not String.IsNullOrEmpty(access.Exception) Then
If report = True Then MsgBox(access.Exception)
Return False
Else
Return True
End If
End Function
Private Sub Savebuttum_Click(sender As Object, e As EventArgs) Handles
Savebuttum.Click
Dim oDate As DateTime = Convert.ToDateTime(DateTimePicker1.Value)
access.addparam("#expensenme", expensenmtXT.Text)
access.addparam("#expensedetail", ExpenseDetailTXT.Text)
access.addparam("#expenseamount", ExpenseAmountTXT.Text)
access.addparam("#expensedate", oDate)
access.addparam("#expensepaidTo", paidtoTXT.Text)
access.ExecQuery("INSERT INTO Expense (Expenses_name, expense_details,
expenses_amount, ExpenseDate_Paid, ExpensePaid_To) Values (#expensenme,
#expensedetail, #expenseamount, #expensedate, #expensepaidTo);")
'report on errors
If Not String.IsNullOrEmpty(access.Exception) Then
MsgBox(access.Exception) : Exit Sub
'success
access.DBDA.Update(access.DBDT)
MsgBox("Expense Has been Added Successfully")
End Sub
End Class
Hum, you have this:
params.ForEach(Sub(p) dbcmd.Parameters.Add(p))
Great, we add the parmaters - looks good to go!!!
then, next line CLEARS all the work above!!! (the parameters are removed!!!!)
'clear params list
params.Clear()
Next up? Many will build a connection object, then a reader, and then a adaptor. But you ONLY need a data adaptor if you going to update a data table. if you just going to execute a command, then you don't need the data table, and you don't need a adaptor FOR that table. Adaptor = ability to modify a existing datatable (or dataset).
You are MUCH better to use the command object.
Why?
Because the command object has a connection object (don't need a separate one)
Because the command object has a data reader for you (no need for a whole data adaptor to JUST fill a table. And remember, you don't need a whole data adaptor UNLESS you are going to send/update a data table back to the database.
And because the command object has the command text, then you don't even need a variable for that!!!
And because all objects are in "one object", then really all you need is something to handy get you the connection.
So, for your insert example, we really don't gain by having that object, do we?
Ok, so here is your insert code without using those extra objects:
so in the following, I declare ONE variable, - the sql command object.
And do the insert
And as FYI? Your save button is not a save - but a insert button - every time you hit it, you will insert a new row. Lets deal with that issue in a bit.
So, here is our code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using cmdSQL As New OleDbCommand("INSERT INTO Expense
(Expenses_name, expense_details,expenses_amount, ExpenseDate_Paid, ExpensePaid_To)
Values (#expensenme,#expensedetail, #expenseamount, #expensedate, #expensepaidTo)",
New OleDbConnection(My.Settings.TESTOLEDB))
With cmdSQL.Parameters
.Add("#expensenme", OleDbType.WChar).Value = expensenmtXT.Text
.Add("#expensedetail", OleDbType.WChar).Value = ExpenseDetailTXT.Text
.Add("#expenseamount", OleDbType.Currency).Value = ExpenseAmountTXT.Text
.Add("#expensedate", OleDbType.DBDate).Value = DateTimePicker1.Value
.Add("#expensepaidTo", OleDbType.WChar).Value = paidtoTXT.Text
End With
cmdSQL.Connection.Open()
cmdSQL.ExecuteNonQuery()
End Using
So things in above:
We have strong data typing and converstion.
Because of this, note how I did NOT have to create a separate date/time variable here.
Note the SAME for "money" or so called currency conversion - again strong data type by using parameters this way.
And is this a date only, or a date+ time value? If it is date, then
.Add("#expensedate", OleDbType.DBDate).Value = DateTimePicker1.Value
If it was/is a date + time, then this:
.Add("#expensedate", OleDbType.DBTimeStamp).Value = DateTimePicker1.Value
So, notice how all your object stuff REALLY did not help one bit, and in fact you did not really save code, and above actually had LESS variables defined to do the whole job.
Now, back to the insert issue/problem. (you save is doing a insert). But what about editing existing records?
So, I would suggest you work out the problem this way:
You create/get/have/assume a data row for the form.
The form takes the data row, fills the controls. You edit, and when you hit save, the data row is sent back to the datbase. So, once this works, then to add? Well, the add code will CREATE a new data row, save to database and THEN you send that new data row to the above eixsting form that can edit a data row, and can save a data row. So the form now is able to deal with both issues (adding vs editing existing). If the user dont' want the row, then you offer a delete button.
And REALLY nice is a data row means you don't deal with SQL, and don't deal with parmaters!!
So the code (desing pattern) I use is thus this:
dim da as oledbDataAdaptor
dim myTable as DataTable = MyRstEdit("SELECT * from tblHotels WHERE ID = " & lngID,da)
dim MyDataRow as DataRow = myTable.Rows(0)
' code to fill controls
txtHotelName.Text = MyDataRow("HotelName")
txtCity.Text = MyDataRow("City")
' etc. etc. etc.
Now to save? Well I put the values back into that DataRow like this:
MyDataRow("HotelName") = txtHotelName.Text
MyDataRow("City") = txtCity.Text
MyDateRow("BookingDate") = txtTimePick1.Value
da.Update(MyTable)
Notice how I don't have parameters, and even strong data type checking occurs for say the above Date/Time booking date column.
And the above is nice, since I don't have to deal with ANY parmaters to udpate a row of data.
The MyRstEdit routine looks like this and returns byREf a "da" (data adaptor).
Public Function MyrstEdit(strSQL As String, Optional strCon As String = "", Optional ByRef oReader As SqlDataAdapter = Nothing) As DataTable
' Myrstc.Rows(0)
' this also allows one to pass custom connection string - if not passed, then default
' same as MyRst, but allows one to "edit" the reocrdset, and add to reocrdset and then commit the update.
If strCon = "" Then
strCon = GetConstr()
End If
Dim mycon As New SqlConnection(strCon)
oReader = New SqlDataAdapter(strSQL, mycon)
Dim rstData As New DataTable
Dim cmdBuilder = New SqlCommandBuilder(oReader)
Try
oReader.Fill(rstData)
oReader.AcceptChangesDuringUpdate = True
Catch
End Try
Return rstData
End Function
So, now in vb.net, I actually find it is LESS code then even writing + using recordsets in MS-Access VBA code.
However, BEFORE you go down ANY of the above road?
Have you considered using the vb.net data binding features. Data-binding in vb.net means that you do NOT write ANY of the above code. it means that vb.net will do all of the dirty work, and write and setup ALL OF the code for you to edit data on a form. The end result is you don't write any code to update a table.
You do have to use + create a "data set". Once done, then you just drag controls onto the form, and you even get this. So you just drop in a dataset, table adaptor, Binding navagator, and you get this:
Note now the tool bar at the top (and you can place it on teh bottom if you wish). So you get this:
So that WHOLE form was created without having to write ONE line of code. And you can see we have navigation, edits and saves and even the ability to add. So, you can build up a editing form - and it thus becomes similar to say working in MS-Access and ZERO lines of code is required to build the above form.
However, if you ARE going to roll your own code? Then use a data row. That way you can shuffle data to/from the table, and NOT have to use parmaters and SQL update and insert statements - but ONLY have nice clean code in which you shove, or get values from that data row. .net will "write" all the update stuff for you.
I have been able to figure out how to populate my combo box with information pulled from a SQL query. What I need to do now is take the item selected from that combo box and run another query with that info using a button. This is what I have so far.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim myconn As New SqlClient.SqlConnection("server=myserver;UID=User;PWD=Password;database=myDB")
Dim myTable As New DataTable()
Dim myCmd As New SqlCommand()
Dim myAdapter As New SqlDataAdapter(myCmd)
myCmd.Connection = myconn
myCmd.Connection.Open()
myCmd.CommandText = "UPDATE myDB.<*Selected list from combo box*> SET example = x WHERE example = y"
myCmd.ExecuteNonQuery()
myCmd.Connection.Close()
MsgBox("Done!")
End Sub
As you can see the issue is the portion I have named <Selected list from combo box> I am not asking for answers on how to do this, I am asking that you all point me in the right direction if possible.
If I understand your question, I believe all you need to do is build a string for your update statement to concatenate the table name in. Something like this would be functional:
myCmd.CommandText = "UPDATE myDB." & listbox1.SelectedItem.Value & " SET example = x WHERE example = y"
One important thing you want to keep in mind though is that a bad value in your list box could cause you to a security vulnerability called SQL Injection. So within your button procedure you might want to do some validation checks to be absolutely sure that the value in listbox1.SelectedItem.Value is strictly a valid table name that you allow to be updated before just passing it along as a SQL command.
I have linked one of my VB.net forms to an MS Access database table, However, when I save the data entered from the VB form into the data set, the data will show in the data set but it will not appear in my database table. Is there anyway I can get the data that I have stored in the data set to appear in the actual database.
I have bound the form fields correctly to the database table and I have added the tables to the form via a data grid.
![Form & Data grid][1]
Can anyone tell me why it won't save to my database? or does anyone have an alternative method for binding forms to a database.
*Note - My database also has a password on it (don't know if that effects it)
*Note - I'm new to this forum so i cant post an image...
*Note - Let me know if you need to see more code
Form loading code
Public Class CD_FORM
Private Sub CD_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
'TODO: This line of code loads data into the 'CDKingsDatabaseDataSet.CD' table. You can move, or remove it, as needed.
Me.CDTableAdapter.Fill(Me.CDKingsDatabaseDataSet.CD)
End Sub
ADD Button code
Private Sub Button5_Click(sender As System.Object, e As System.EventArgs) Handles add.Click
CDBindingSource.AddNew()
End Sub
SAVE Button code
Private Sub Button6_Click(sender As System.Object, e As System.EventArgs) Handles sav.Click
CDBindingSource.EndEdit()
CDTableAdapter.Update(CDKingsDatabaseDataSet.CD)
End Sub
Ah Ok I've found the solution thanks to a comment by jmcilhinney on the "VB.NET - .accdb database not saving changes" question made a while back.
The changes to my database have been made but the database I was looking at was in the wrong location.
The database which stored the changes was in my bin/Debug folder.
At the company I work for all applications pull information from that database, I have decided to write a detailed answer to answer how different databases can let the user know they have been altered. I will answer for the following types:
Access
SQL
Oracle
File systems (Files and folders)
Why I have done this?...... The company I work for have many different databases and applications that use these databases. However the applications spend a lot of time within the database checking to see if the data has been changed. I have complied this list to show how certain databases/files can use different tools to let an application know it has been changed. So an event can be fired off. This will hopefully reduce computing power and speed up the applications.
Please edit as you seem fit. If you need any other information a comment would be great. I am still in the process of adding the Oracle database solution and editing the Access and SQL.
Access and FileSystems/Files
For the access point I have used a SystemFileWatcher. This keeps an eye on the database and if it has been modified it will run the code to get the new data from the database. This means that the application is not constantly going in to the database and grabbing new data when it is not needed.
The FileSystemWatcher can run different code from the events such as name changed, moved or modified. I only need to use the modified. I have got the database path from an XML File I am using which means it is not hard coded and can be changed from the xml file and the watcher will watch else where.
Protected Overrides Sub OnStart(ByVal args() As String)
Dim g1 As New FileSystemWatcher()
g1.Path = GetSingleNode(XmlFileName, "data/G1Path")
g1.NotifyFilter = (NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName)
g1.Filter = GetSingleNode(XmlFileName, "data/G1Filter")
AddHandler g1.Changed, AddressOf OnChanged
g1.EnableRaisingEvents = True
Dim g2 As New FileSystemWatcher()
g2.Path = GetSingleNode(XmlFileName, "data/G2Path")
g2.NotifyFilter = (NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName)
g2.Filter = GetSingleNode(XmlFileName, "data/G2Filter")
AddHandler g2.Changed, AddressOf OnChanged
g2.EnableRaisingEvents = True
End Sub
Protected Overrides Sub OnStop()
End Sub
Public Shared Function GetSingleNode(ByVal xmlPath As String, ByVal nodeLocation As String) As String
Try
Dim xpathDoc As New XPathDocument(xmlPath) 'gets the nodes from the XML file ready to be put in to the network path/variables
Dim xmlNav As XPathNavigator = xpathDoc.CreateNavigator()
Return xmlNav.SelectSingleNode(nodeLocation).Value
Catch ex As Exception
Throw
End Try
End Function
After this I simply have an on changed function. Hope this helps anyone that needs it.
FileSystems/Files
For the file path and system paths the code above is very similar just using different paths and filters to get the certain types or names of files. This will then run the code if these have been changed/modified. If anybody would like code for this please write a comment and I can supply some.
SQL Databases
In the SQL databases there are multiple ways of checking to see if the data has been changed. I will reference a few MSDN Pages along with another question to provide information to these. However the way I used was slightly different as I didn't have a service broker running and no queues were enabled on my SQL databases.
Is there something like the FileSystemWatcher for Sql Server Tables?
http://msdn.microsoft.com/en-us/library/62xk7953.aspx#Y342
However the way I used was by using a checksum and a timer and checking the checksum on a loop to see if the database was changed. As the 'hash' always changes if the data is changed:
http://sqlserverplanet.com/design/how-to-detect-table-changes
http://www.mssqltips.com/sqlservertip/1023/checksum-functions-in-sql-server-2005/
My Code:
'Within main to get the first checksum value ready to be comapred with at a later date. These are global variables
Dim newdata As DataTable = SQLMethods.ExecuteReader(ConnectionString1, "SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM Alarms")
checksum = newdata.Rows(0).Item(0)
Timer1.Start()
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Timer1.Stop()
Dim newdata As DataTable = SQLMethods.ExecuteReader(ConnectionString1, "SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM Alarms")
checksumNew = newdata.Rows(0).Item(0)
If checksum <> checksumNew Then
MsgBox("Hello")
checksum = checksumNew
End If
Timer1.Start()
End Sub
As you can see if they do match the checksum is changed to match so the next time it happens they will be the same unless the database is indeed changed. I have stopped and then restarted the time to avoid confusion of the message box, however the message box is used for debugging purposes as an event could be fired here or what ever code was wanting to happen if the database was changed.
Oracle
After doing research I have not been able to implement this solution in my own application but hopefully it will provide info to other users. In Oracle there is something called OracleDependencyClass which provides an application a notification if the chosen data has been modified. I will supply some hyper-links that have some examples and the basics of how to use these in the hope someone doesn't need to mirror my own research.
Developing Applications with Database Change Notification
OracleDependency Class
Oracle® Data Provider for .NET Developer's Guide - OracleDependency Class(2)
Example of using the class in C# and VB.NET
If these pages don't help there are plenty of other webpages that you can access if you either search for "oracle dependency", "OracleDependency Class" and "Database Change Notification".
I followed an example on stackoverflow about how to read database records into variables. This is the first time doing this and I feel that I'm close but I'm baffled at this point about the problem.
Here is the link I am referring to:
Visual Basic 2010 DataSet
My code is shown below.
Public Class Form1
' DataSet/DataTable variables
Dim testdataDataSet As New DataSet
Dim dttestdataDataTable As New DataTable
Dim datestdataDataAdapter As New Odbc.OdbcDataAdapter
' Variables for retrieved data
' Dim sSpeed As String = ""
' Dim sFuelprice As String = ""
Dim sSpeed As Integer
Dim sFuelprice As Integer
'Connect to the database
''
'Fill DataSet and assign to DataTable
datestdataDataAdapter.Fill(TestdataDataSet , "TestdataDataSet")
dttestdataDataTable = TestdataDataSet.Tables(0)
'Extract data from DataTable
' Rows is the row of the datatable, item is the column
sSpeed = dtTestdataDataTable.Rows(0).Item(0).ToString
sFuelprice = dtTestdataDataTable.Rows(0).Item(1).ToString
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
result.Text = Val(miles.Text) * sSpeed * sFuelprice
End Sub
End Class
Basically, I am getting declaration errors and I don't understand why since the example I was following clearly declared them. I'm connecting to an Access DB called "testdata.mdb" which contains only 1 record with two fields that I want to use throughout the program. The exanple said to dim the variables for each field as strings but this created more dim errors so I made them into integers and remarked out the original dim statements in the meantime (since they're going to be used in calculations.) The dataadapter and datatable variables also are getting flagged for not being declared when they were earlier in the program. I know this must be a simple thing to fix but I'm just not seeing it.
The form is just a simple thing where the user types in a number and a result is produced by using the numbers read in from the database. In short, I want to be able to do simple calculations with a database within a program and the dim statement thing is getting in the way.
If someone can please clarify what I should do, that would be very much appreciated. Thanks!
When you're trying to learn new technology, it's usually best to work from the outside in. The "outside-most" object in your case seems to be an OdbcConnection object.
Public Class Form1
Const connectionString as String = "Driver={Microsoft Access Driver (*.mdb)};DBQ=c:\bin\Northwind.mdb"
Dim connection As New OdbcConnection(connectionString)
connection.Open()
connection.Close()
end Class
Resolve errors at that level first. Then add a declaration for the data adapter--only for the data adapter--and resolve any errors with that. Repeat until you finish your class.
See OdbcConnectionString, and refer to the connection string web site if you need to.
I apologize for the delay in writing the answer to close off this question and sum it up.
In my case, it dataset is a one row database called testdata and contains 20 fields.
The solution that worked is as follows:
Immediately after the form1_load event, the variables can be immediately written after the dataadapter line:
Me.TestdataTableAdapter.Fill(Me.fooDataSet.testdata)
speed = Me.fooDataSet.testdata(0).speed
fuelprice = Me.fooDataSet.testdata(0).fuelprice
mpg = Me.fooDataSet.testdata(0).mpg
Then just DIM the variables to whatever you want them to be (in this case, speed is an integer, fuelprice is a decimal and MPG is a decimal) right after the PUBLIC CLASS statement at the top of your form's code.
You can then manipulate the variables in calculations after a button click as such:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
fuelcost = CDec((miles.Text/ mpg) * fuelprice)
txtfuelcost.Text = fuelcost.ToString
End Sub
Thank you all for your responses!