I have a question about database values and how to determine the id of a value that has been changed by the user at some point.
As it is currently set up there is a combobox that is populated from a dataset, and subsequent text boxes whose text should be determined by the value chosen from that combobox.
So let's say for example you select 'Company A' from the combobox, I would like all the corresponding information from that company's row in the dataset to fill the textboxes (Name = Company A, Address = 123 ABC St., etc.,)
I am able to populate the combobox just fine. It is only however when I change the index of the combobox that this specific error occurs:
An unhandled exception of type 'System.Data.OleDb.OleDbException'
occurred in System.Data.dll
Additional information: Data type mismatch in criteria expression.
Here is the corresponding code:
Imports System.Data.OleDb
Public Class CustomerContact
Dim cn As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|datadirectory|\CentralDatabase.accdb;")
Dim da As New OleDbDataAdapter()
Dim dt As New DataTable()
Private Sub CustomerContact_Load(sender As Object, e As EventArgs) Handles MyBase.Load
cn.Open()
da.SelectCommand = New OleDbCommand("select * from Customers", cn)
da.Fill(dt)
Dim r As DataRow
For Each r In dt.Rows
cboVendorName.Items.Add(r("Name").ToString)
cboVendorName.ValueMember = "ID"
Next
cn.Close()
End Sub
Private Sub cboVendorName_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboVendorName.SelectedIndexChanged
cn.Open()
da.SelectCommand = New OleDbCommand("select * from Customers WHERE id='" & cboVendorName.SelectedValue & "'", cn)
da.Fill(dt)
Dim r As DataRow
For Each r In dt.Rows
txtNewName.Text = "Name"
txtAddress.Text = "Address"
Next
cn.Close()
End Sub
The error is caught at Line 24 of this code, at the second da.Fill(dt) . Now obviously from the exception I know that I am sending in a wrong datatype into the OleDbCommand, unfortunately I am a novice when it comes to SQL commands such as this. Also please keep in mind that I can't even test the second For loop, the one that is supposed to fill the Customer information into textboxes (for convenience I only copied the first two textboxes, of which there are nine in total). I am think I could use an If statement to determine if the row has been read, and from there populate the textboxes, but I will jump that hurdle when I can reach it.
Any guidance or suggestions would be much appreciated. Again I am a novice at managing a database and the code in question pertains to the project my current internship is having me write for them.
Since you already have all the data from that table in a DataTable, you dont need to run a query at all. Setup in form load (if you must):
' form level object:
Private ignore As Boolean
Private dtCust As New DataTable
...
Dim SQL As String = "SELECT Id, Name, Address, City FROM Customer"
Using dbcon = GetACEConnection()
Using cmd As New OleDbCommand(SQL, dbcon)
dbcon.Open()
dtCust.Load(cmd.ExecuteReader)
End Using
End Using
' pk required for Rows.Find
ignore = True
dtCust.PrimaryKey = New DataColumn() {dtCust.Columns(0)}
cboCust.DataSource = dtCust
cboCust.DisplayMember = "Name"
cboCust.ValueMember = "Id"
ignore = False
The ignore flag will allow you to ignore the first change that fires as a result of the DataSource being set. This will fire before the Display and Value members are set.
Preliminary issues/changes:
Connections are meant to be created, used and disposed of. This is slightly less true of Access, but still a good practice. Rather than connection strings everywhere, the GetACEConnection method creates them for me. The code is in this answer.
In the interest of economy, rather than a DataAdapter just to fill the table, I used a reader
The Using statements create and dispose of the Command object as well. Generally, if an object has a Dispose method, put it in a Using block.
I spelled out the columns for the SQL. If you don't need all the columns, dont ask for them all. Specifying them also allows me to control the order (display order in a DGV, reference columns by index - dr(1) = ... - among other things).
The important thing is that rather than adding items to the cbo, I used that DataTable as the DataSource for the combo. ValueMember doesn't do anything without a DataSource - which is the core problem you had. There was no DataSource, so SelectedValue was always Nothing in the event.
Then in SelectedValueChanged event:
Private Sub cboCust_SelectedValueChanged(sender As Object,
e As EventArgs) Handles cboCust.SelectedValueChanged
' ignore changes during form load:
If ignore Then Exit Sub
Dim custId = Convert.ToInt32(cboCust.SelectedValue)
Dim dr = dtCust.Rows.Find(custId)
Console.WriteLine(dr("Id"))
Console.WriteLine(dr("Name"))
Console.WriteLine(dr("Address"))
End Sub
Using the selected value, I find the related row in the DataTable. Find returns that DataRow (or Nothing) so I can access all the other information. Result:
4
Gautier
sdfsdfsdf
Another alternative would be:
Dim rows = dtCust.Select(String.Format("Id={0}", custId))
This would return an array of DataRow matching the criteria. The String.Format is useful when the target column is text. This method would not require the PK definition above:
Dim rows = dtCust.Select(String.Format("Name='{0}'", searchText))
For more information see:
Using Statement
Connection Pooling
GetConnection() method aka GetACEConnection
Related
I have an SQL query that returns 2 columns of values:
country | number
NA | 1
IN | 2
CN | 3
DE | 4
And so on.
I am trying to do one of the following:
Assign these values to variables I can copy to an excel workbook
Or just use the DGV as a medium to copy values to text boxes.
For example, I have a form with country labels and textboxes next to them. I would want to click a button and have the data copied to the matching text box.
DGV number value where DGV row value = CN would be 3 and that value would be copied to the CN value text box.
If you are only using the DGV as a medium, but not actually displaying it, use a dictionary instead. Output the SQLDataReader using the SQLcommand(cmd) with the country code being the key and the number being the value. Then its as simple as:
Dim dc As Dictionary(Of String, String) = New Dictionary(Of String, String)
Dim cmd As SqlCommand = "your query string"
cmd.Connection.ConnectionString = "your con string"
Using sqlrdr As SqlDataReader = cmd.ExecuteReader()
While (sqlrdr.Read())
dc.Add(sqlrdr(0), sqlrdr(1))
End While
End Using
Then just output to the textbox:
txtNA.Text = dc.Item("NA")
If the countries are fixed as your question refers, then you could use something like this:
First, use the names of the countries to name the TextBoxes (txtNA, txtIN, txtCN,...)
Then you can put this code:
Try
For i = 0 To DataGridView1.RowCount - 1
Me.Controls("txt" & DataGridView1.Rows(i).Cells(0).Value.ToString).Text = _
DataGridView1.Rows(i).Cells(1).Value.ToString()
Next
Catch
End Try
The following will not work 'as is' if using classes via dragging tables from the data source window in the ide. We would need to cast objects not to a DataTable but to the class definition in a TableAdapter, DataSet and BindingSource.
Here is a method which reads from SQL-Server database table, places data into a DataTable, data table become the data source for a BindingSource which becomes the data source for the DataGridView. Using a BindingSource we now use the BindingSource to access row data rather than the DataGridView and it's always best to go to the data source rather than the user interface.
BindingSource.Current is a DataRowView, drill down to Row property then field language extension method to get strongly typed data for the current row if there is a current row as you will note I am using two forms of assertions, first, do we have a data source and is the data source populated then we see if there is indeed a current row.
From here we can set variable, properties or control text to the field values of the current row.
Note in form load I seek a specific country (totally optional) and then if found go to that row.
Least but not last, I like using xml literals when doing SQL in code so there is no string concatenation and we can format the statement nicely.
Public Class Form1
''' <summary>
''' Permits obtaining row data in DataGridView
''' </summary>
''' <remarks></remarks>
Dim bsCountries As New BindingSource
Public Property Country As String
Public Property CountryNumber As Integer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim dt As New DataTable
Using cn As New SqlClient.SqlConnection With
{
.ConnectionString = My.Settings.KarenDevConnectionString
}
Using cmd As New SqlClient.SqlCommand With {.Connection = cn}
' xml literal to make command text
cmd.CommandText =
<SQL>
SELECT [ID],[Country],[Number]
FROM [Countries]
</SQL>.Value
cn.Open()
dt.Load(cmd.ExecuteReader)
dt.Columns("ID").ColumnMapping = MappingType.Hidden
bsCountries.DataSource = dt
DataGridView1.DataSource = bsCountries
' let's try and move to a country
Dim index As Integer = bsCountries.Find("Country", "CN")
If index > -1 Then
bsCountries.Position = index
End If
End Using
End Using
End Sub
''' <summary>
''' Put field values into TextBoxes
''' </summary>
''' <remarks></remarks>
Private Sub DoWork()
If bsCountries.DataSource IsNot Nothing Then
If bsCountries.Current IsNot Nothing Then
Dim row As DataRow = CType(bsCountries.Current, DataRowView).Row
TextBox1.Text = row.Field(Of String)("Country")
TextBox2.Text = row.Field(Of Integer)("Number").ToString
' we can also do this
Me.Country = row.Field(Of String)("Country")
Me.CountryNumber = row.Field(Of Integer)("Number")
End If
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
DoWork()
End Sub
End Class
I searched many forums but didn't find any solution. I want to update access table cells from Vb.net. My table has fields:
[PanelNumber],[Date], [PVValue]
In Panel number field, there is some text like "Panel 1", "Panel 2" etc..
from vb, i will select that "Panel 1" after clicking a button, i need to fill that "PVValue" field with random numbers in given range, plz check my code below, when i try with this code, i am always getting same number in all rows
but need separate number (may be repeated in some rows)
LogTable2 is my table name
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
connString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=LoggedData.accdb;Jet OLEDB:Database Password=GodavarthiSuresh;"
myNewConnection.ConnectionString = connString
myNewConnection.Open()
Dim UpdateString As String = "update LogTable2 set [pvvalue]= #rndVal1 where panelnumber='" & panelnametxt.Text & "'"
Dim UpdateCmd As New OleDb.OleDbCommand(UpdateString, myNewConnection)
UpdateCmd.Parameters.Clear()
Randomize()
UpdateCmd.Parameters.AddWithValue("#rndVal1", GetRandom())
Try
UpdateCmd.ExecuteNonQuery()
UpdateCmd.Dispose()
myNewConnection.Close()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
' this is the function to get random number in given range
Public Function GetRandom() As Integer
Static Generator As System.Random = New System.Random()
Return Generator.Next(825, 850)
End Function
If you have multiple rows for each panel and you want them to have different values, you need to update them individually. Its is not true that update command will be "called" 5 times if there are five rows associated. It will be executed once per click event.
To do what it sounds like you want, you need a unique identifier for each such as an AutoIncrement ID column.
Private RNG As New Random()
Private Sub btnUpdate_Click(etc...
Dim sql = "SELECT ID FROM LogTable2 WHERE panelnumber = #pnl"
Dim pnlList As New List(Of Int32)
Using con As OleDbConnection = GetACEConnection()
Using cmd As New OleDbCommand(sql, con)
con.Open()
cmd.Parameters.AddWithValue("#pnl", panelnametxt.Text)
' get affected row IDs into a list;
Using rdr As OleDbDataReader = cmd.ExecuteReader
While rdr.Read
pnlList.Add(Convert.ToInt32(rdr.Item("ID")))
End While
End Using ' close, dispose of reader
End Using ' dispose of cmd
' not sure you need a new command object
sql = "UPDATE LogTable2 SET pvvalue = #rVal WHERE ID = #id"
Using pcmd As New OleDbCommand(sql, con)
' loop thru ID list and update each row with
' new random value 825-849 inclusive
For n As Int32 = 0 To pnlList.Count - 1
pcmd.Parameters.AddWithValue("#rVal", RNG.Next(825, 850))
pcmd.Parameters.AddWithValue("#id", pnlList(n))
pcmd.ExecuteNonQuery()
' clear for next iteration
pcmd.Parameters.Clear()
Next
End Using ' close and dispose of pcmd
End Using ' close and dispose of connection
End Sub
I dont like scattering the connection string in every method which opens a connection, so a method for that is nice to have.
Notes:
This depends on a unique ID column which is AutoIncrement (PK). If you have some other unique identifier, use it but you have to have some way to identify rows individually.
Rather than a method to create a random value, since it is just one line, it might be easier to just use your RNG directly as shown.
I cant test the code, but it should be close.
Use Using blocks to close and dispose of DBObjects like connections, command and reader otherwise you can run out of resources.
You can also initialize Command objects with the SQL and COnnection when you declare it rather than setting them as properties. It makes the code a little more compact and less likely that you forget them.
Randomize does nothing - it is meant to be used with the old VB6 Rnd(). You only need to [Escape] keywords in SQL, not every column name and pvvalue is not a keyword.
A DataTable instead of a Reader could be used to get the rows but I am not sure it is any simpler.
Finally, elements of a SQL WHERE clause can also be parameterized; there is no need to concat them just because it is a where rather than a column value.
you can do this in database level,add auto increment value to database field
I can view the data in textbox but the problem is, if I change it to drop down to show multiple data, it will give an empty data ..
Private Sub textbox2_SelectionChangeCommitted(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox2.KeyPress
Try
cmd = New Odbc.OdbcCommand("SELECT maker FROM pcba_info.tblvendorpartnumber WHERE partnumber ='" & Trim(TextBox2.Text.TrimEnd()) & "'", con)
dr = cmd.ExecuteReader
If dr.Read Then
TextBox55.Text = dr("maker").ToString ----------> return single data
TextBox12.SelectedIndex = dr("maker").ToString -----------> no data
dgvcertifiedoperator.DataSource = dt
dgvcertifiedoperator.Update()
dgvcertifiedoperator.Refresh()
End If
Catch ex As Exception
Debug.WriteLine("Plz check the parts" & ex.Message)
End Try
End Sub
I am assuming that your TextBox12 represents a dropdown. In this case assigning SelectedIndex to a string is incorrect (just think about it for a second - an index cannot be a string, i.e. AAA is not an index). Use Option Strict On to prevent such errors.
See, by default, VB.NET does not prevent you from doing it, because theoretically a string can be an integer, so this statement can work. But it does not mean it will work 100% of the time. Option Strict validates type conversion to ensure it always works, or your code does not compile.
Back to your question, one way to do it is to accumulate values in a list, then make that list a data source for your dropdown, like this:
Dim lst As New List(Of String)
While dr.Read Then
lst.Add(dr("maker").ToString)
End While
TextBox12.DataSource = lst
You can then retrieve current value by using TextBox12.SelectedItem, cast to string:
DirectCast(TextBox12.SelectedItem, String)
A side note - make sure your controls are named accordingly. TextBox is usually reserved for, well, text boxes. Dropdowns are usually MakerComboBox, MakerDropDown etc. Avoid naming your controls like TextBox56 and ComboBox188, because those numbers are meaningless for another developer. Even if you are the only one on the project, consider us helping you with it.
I am adding a tab page and datagridview to a Tab Control for every record in a datatable.
I would like to have a new Tab/DataGridView for each record (there will be ~3 for right now). I am declaring a new DataGridView D. How do I refer to these controls later?
I will want to do things like save changes in the datagridview to the database. Currently I can get the data on the screen and it looks good, but I believe I am not adding the DataGridView controls correctly because I keep re-using "D" as the control.
Dim dt As New DataTable
GetDataTable("SELECT * FROM aFeeTypes DescSeq", dt)
Dim i As Integer
'for each class in the datatable add a tab and a datagridview
For i = 0 To dt.Rows.Count - 1
Dim dr As DataRow
dr = dt.Rows(i)
Dim D = New DataGridView
D.Visible = True
Dim tp As New TabPage
tp.Name = "tp" & i
tp.Text = dr.Item("Desc2")
frmUI.tcFee.TabPages.Add(tp)
frmUI.tcFee.TabPages(i).Controls.Add(D)
dgv_Fill(D, "SELECT * FROM Fee WHERE ClassID=" & dr.Item("ClassID") & " ORDER BY Seq")
D.AutoResizeColumns()
D.Width = tp.Width
D.Height = tp.Height
Next i
this does not work:
With frmUI.Controls("D" & i)
.AutoResizeColumns()
.Width = tp.Width
.Height = tp.Height
End With
D is purely the variable name in the scope you are using it in.
You need to give the control a unique Name that yo can reference it by later.
The Name property can be used at run time to evaluate the object by
name rather than type and programmatic name. Because the Name property
returns a String type, it can be evaluated in case-style logic
statements (Select statement in Visual Basic, switch statement in
Visual C# and Visual C++).
I found a solution to the problem. The problem is that the new row has an autonumber ID.
When da.update(dt) occurs, the new row is inserted to the database. The database knows the new autonumber ID. However, the datatable does not.
For the datatable to know, the dataadapter is refilled. However, this causes the datatable to have all the old rows plus all the new rows and they all appear in the datagridview.
By clearing the old rows from the datatable, the fill refreshes all the data in the datatable and therefore refreshes the datagridview.
Public Sub dgv_AddRow(ByVal dgvName As String)
Dim dgv As DataGridView = colDataGridView.Item(dgvName)
Dim da As SqlDataAdapter = colDataAdapter.Item(dgvName)
Dim dr As DataRow = frmUI.allDataSet.Tables(dgvName).NewRow
'autopopulate the class id
dr("ClassID") = dgv.Parent.Tag
'add the new row
frmUI.allDataSet.Tables(dgvName).Rows.Add(dr)
'get the changed table
Dim dt As DataTable = frmUI.allDataSet.Tables(dgvName)
'inserts the new row to the database. concurrency violation
'will occur because datatable does not know the new Autonumber ID for the new row.
da.Update(dt)
'remove the datatable rows so they can be replaced using the adapter fill
'if rows are not cleared, following fill causes datatable to
'have existing rows plus all the rows again due to the fill
frmUI.allDataSet.Tables(dgvName).Rows.Clear()
'refill the adapter (refill the table)
'Everything you do to the underlying dataTable
'gets displayed instantly on the datagridview
da.Fill(frmUI.allDataSet, dgvName)
End Sub
I am currently using VB. I want to do a Calendar Control which have dates highlighted/selected. All these dates are retrieved from a database.
First thing I need to know is how to put all the dates into an array
Second thing I need to know is how to highlight all the dates in the array.
I have done some research on the internet and they said something about selectedDates and selectedDates collection and dayrender. But frankly speaking, I can't really find any VB codes regarding this. Dates format will be in dd/MM/yyyy
Imports System.Data.SqlClient
Partial Class _Default
Inherits System.Web.UI.Page
Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
Dim connectionString As String = ConfigurationManager.ConnectionStrings("CleanOneConnectionString").ConnectionString
Dim connection As SqlConnection = New SqlConnection(connectionString)
connection.Open()
Dim sql As String = "Select schedule From OrderDetails Where schedule is not null"
Dim command As SqlCommand = New SqlCommand(sql, connection)
Dim reader As SqlDataReader = command.ExecuteReader()
If (reader.Read()) Then
If (reader.HasRows) Then
While reader.Read()
myCalendar.SelectedDates.Add(CType(reader.GetDateTime(0), Date))
End While
End If
End If
reader.Close()
connection.Close()
myCalendar.SelectedDayStyle.BackColor = System.Drawing.Color.Red
End Sub
End Class
My Calendar
<asp:Calendar ID="myCalendar" runat="server" ShowGridLines="True">
</asp:Calendar>
Updated with what I have done, but still does not show
Thanks for the help
Assume you have a DataTable named myDates, and a Calendar control named myCalendar:
For i As Int = 0 To myDates.Rows.Count - 1
myCalendar.SelectedDates.Add(CType(myDates.Row(i)(0), Date)
Next
You can declare the highlighting in your markup:
<asp:Calendar ID="myCalendar" runat="server">
<SelectedDayStyle BackColor="red" />
</asp:Calendar>
Or programatically:
myCalendar.SelectedDayStyle.BackColor = System.Drawing.Color.Red
UPDATE For SqlDataReader (VB.NET this time)
If reader.HasRows Then
While reader.Read()
myCalendar.SelectedDates.Add(CType(reader(0), Date)
End While
End If
UPDATE based on OP's code
Are you getting any errors when the code runs? SqlDataReader.GetDateTime will throw an InvalidCastException if the column being read isn't a DateTime column.
I'm wondering if it's a format issue? Can you verify the data type of the column in the database, as well as the format the date is being stored in?
I've modified your code a bit with a couple of suggestons.
Imports System.Data.SqlClient
Partial Class _Default Inherits System.Web.UI.Page
Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
Dim connectionString As String = ConfigurationManager.ConnectionStrings("CleanOneConnectionString").ConnectionString
' Using blocks will automatically dispose of the object, and are
' pretty standard for database connections
Using connection As New SqlConnection(connectionString)
connection.Open()
Dim sql As String = "Select schedule From OrderDetails Where schedule is not null"
Dim command As SqlCommand = New SqlCommand(sql, connection)
Dim reader As SqlDataReader = command.ExecuteReader()
' This is not needed - in fact, this line will "throw away"
' the first row in the row collection
'If (reader.Read()) Then
If (reader.HasRows) Then
While reader.Read()
myCalendar.SelectedDates.Add(CType(reader.GetDateTime(0), Date)) End While
End If
reader.Close()
' Not needed because of the Using block
'connection.Close()
End Using
myCalendar.SelectedDayStyle.BackColor = System.Drawing.Color.Red
End Sub
End Class
For the second part of your question, you can see on this post how to highlight specified days, albeit using C# syntax.
I'm going to assume a L2S format is being used to fetch the dates for now (comment with actual implementation if you need better detail).
I would recommend the dates you want to select be held in a variable on the form (instead of scoped to the function) to prevent database queries running everytime a day is rendered. With that in mind, here is some sample code (free-hand so please excuse and comment on basic/troubling syntax issues):
Private DatesToHighlight As IEnumerable(Of Date)
' implementation details provided so commented this bit out, see EDIT below
'Protected Sub PopulateDatesToHighlight()
' DatesToHighlight = db.SomeTable.Select(Function(n) n.MyDateField)
'End Sub
Protected Sub DayRenderer(ByVal object As Sender, ByVal e As DayRenderEventArgs)
If DatesToHighlight.Contains(e.Day.Date) Then
e.Cell.BackColor = System.Drawing.Color.Red
End If
End Sub
As specified in the question I linked, you'll need to change the markup for the calendar control to provide the ondayrender parameter like so ondayrender="DayRenderer"
Regarding changing your dates to an array, it depends what format they are in at the start. If in anything that inherits from IEnumerable then you can use ToArray(). If they are just variables you can initialise the array with the dates
Dim myDates() As Date = {dateVar1, dateVar2}
Hope that helps?
EDIT: (In response to OP's addition of code)
To get from your data reader to an array (though I'm not convinced you need an array) I would do the following:
' using the variable I declared earlier
DatesToHighlight = New IEnumerable(Of Date)
If reader.HasRows Then
Dim parsedDate As Date
While reader.Read()
If Date.TryParse(reader(0), parsedDate) Then
DatesToHighlight.Add(parsedDate)
End If
End While
End If
Dim myArrayOfDates() As Date = DatesToHighlight.ToArray()