I am trying to select a row from my gridview and then display further information associated with the data from the row. In order to do this I am setting a DataKey which in this case is ID. I then want to access my database and select all the records where the ID is equal to the ID which is the DataKey of the selected row. I am having a bit of trouble with this unfortunately as I am not aware how to correctly access the DataKey of my Selected row. Below is my code. I am trying to take the value of the datakey and set it as an int, which then in return is used in my SELECT statement.
Protected Sub GridView1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles GridView1.SelectedIndexChanged
Dim RegDataConn1 As New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0; data source=" & Server.MapPath("App_Data/1202389.mdb"))
Dim i As Integer = GridView1.SelectedIndex
Dim hotelid As Integer = GridView1.DataKeys(i).Value
Dim cmd1 As OleDbCommand = New OleDbCommand("SELECT * FROM Ratings WHERE Hotel.Hotel_ID=" & hotelid.ToString(), RegDataConn1)
RegDataConn1.Open()
Dim myDA1 As OleDbDataAdapter = New OleDbDataAdapter(cmd1)
Dim myDataSet1 As DataSet = New DataSet
myDA1.Fill(myDataSet1, "Table1")
GVDetailView.DataSource = myDataSet1.Tables("Table1").DefaultView
GVDetailView.DataBind()
RegDataConn1.Close()
End Sub
http://postimg.org/image/yp3ukwsdv/ Here is a picture of my error
Getting the value of the datakey should be:
Dim hotelid As String = GridView1.DataKeys(i).Value.ToString()
Or convert the type to integer if it really is.
What you have missed is to tell the GridView its DataKeyNames.
To do so, change the markup of your gridview into something like:
<asp:GridView ID="GridView1" runat="server" DataKeyNames="hotelid">
</asp:GridView>
Your query string is not dynamic
"SELECT Service_Rating, Price_Rating, Clean_Rating, Location_Rating, Overall_Rating, Text_Review FROM Ratings WHERE Hotel.Hotel_ID=hotelid"
Should be
"SELECT Service_Rating, Price_Rating, Clean_Rating, Location_Rating, Overall_Rating, Text_Review FROM Ratings WHERE Hotel.Hotel_ID=" & hotelid.ToString
Although this may resolve your problem I would recommend using stored procedures, rather than dynamic SQL. Its faster and more secure.
Related
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
When I run this code it is also retrieving some other fields which are not present in the table. How can I overcome this?
Dim conn As New OleDb.OleDbConnection
'Create a connection string for an Access database
Dim strConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=F:\check\a.mdb"
'Attach the connection string to the connection object
conn.ConnectionString = strConnectionString
'Open the connection
conn.Open()
Dim Restrictions() As String = {Nothing, Nothing, selected, Nothing}
Dim CollectionName As String = "Columns"
Dim dt As DataTable = conn.GetSchema(CollectionName, Restrictions)
For Each TableRow As DataRow In dt.Rows
ComboBox1.Items.Add(TableRow.Item("COLUMN_NAME"))
The additional columns which is also retrieved are:
1.ID
2.Date create
3.Date update
4.Id
5.Lv
6.Name
7.parent Id
8.Type
9.GUID
10.Id
... and 6 more. The original schema consists of only 5 fields.
Your problem is simply that the variable selected has the value Nothing when you insert it into the Restrictions() array. When I run the following code I only get the column names for the table named [new]:
Option Strict On
Imports System.Data.OleDb
Module Module1
Sub Main()
Dim connStr As String =
"Provider=Microsoft.ACE.OLEDB.12.0;" &
"Data Source=C:\Users\Public\test\so34490626\a.mdb"
Using conn As New OleDbConnection(connStr)
conn.Open()
Dim selected As String = "new"
Dim Restrictions() As String = {Nothing, Nothing, selected, Nothing}
Dim CollectionName As String = "Columns"
Dim dt As DataTable = conn.GetSchema(CollectionName, Restrictions)
For Each TableRow As DataRow In dt.Rows
Console.WriteLine(TableRow.Item("COLUMN_NAME"))
Next
End Using
End Sub
End Module
The result is:
GENDER
MEMBER OF RISHI PRASAD
NAME OF SADHAK
PROFESSION
However, when the value of selected is Nothing ...
Dim selected As String = Nothing
... I get the "extra" columns you describe
DateCreate
DateUpdate
Id
Lv
Name
ParentId
Type
Attributes
DataType
FieldName
IndexType
SkipColumn
SpecID
Start
Width
DateDelim
DateFourDigitYear
DateLeadingZeros
DateOrder
DecimalPoint
FieldSeparator
FileType
SpecID
SpecName
SpecType
StartRow
TextDelim
TimeDelim
GENDER
MEMBER OF RISHI PRASAD
NAME OF SADHAK
PROFESSION
The reason is revealed if I change the Console.WriteLine to include the table names:
Console.WriteLine("[{0}].[{1}]", TableRow.Item("TABLE_NAME"), TableRow.Item("COLUMN_NAME"))
Then we see:
[MSysAccessStorage].[DateCreate]
[MSysAccessStorage].[DateUpdate]
[MSysAccessStorage].[Id]
[MSysAccessStorage].[Lv]
[MSysAccessStorage].[Name]
[MSysAccessStorage].[ParentId]
[MSysAccessStorage].[Type]
[MSysIMEXColumns].[Attributes]
[MSysIMEXColumns].[DataType]
[MSysIMEXColumns].[FieldName]
[MSysIMEXColumns].[IndexType]
[MSysIMEXColumns].[SkipColumn]
[MSysIMEXColumns].[SpecID]
[MSysIMEXColumns].[Start]
[MSysIMEXColumns].[Width]
[MSysIMEXSpecs].[DateDelim]
[MSysIMEXSpecs].[DateFourDigitYear]
[MSysIMEXSpecs].[DateLeadingZeros]
[MSysIMEXSpecs].[DateOrder]
[MSysIMEXSpecs].[DecimalPoint]
[MSysIMEXSpecs].[FieldSeparator]
[MSysIMEXSpecs].[FileType]
[MSysIMEXSpecs].[SpecID]
[MSysIMEXSpecs].[SpecName]
[MSysIMEXSpecs].[SpecType]
[MSysIMEXSpecs].[StartRow]
[MSysIMEXSpecs].[TextDelim]
[MSysIMEXSpecs].[TimeDelim]
[new].[GENDER]
[new].[MEMBER OF RISHI PRASAD]
[new].[NAME OF SADHAK]
[new].[PROFESSION]
The "MSys*" tables are system tables that are hidden by default in the Access user interface.
Before looping on your table rows, you need to identify the valid/permanent columns of your dataTable.
To do so, you should first browse the columns collection of your datatable object. By checking the properties of each one of these columns, you will be able to identify the temporary ones. I guess it might be hidden somewhere in the 'extended properties' of the DataColumn object.
In order to identify the right property, you will go through something like this (written on the fly ...):
For each tableColumn as DataColumn in dt.Columns
Console.WriteLine(tableColumn.[propertyName].ToString())
...
Next
I do not know exactly which one of the properties will let you know if the column is part of the original table fields. You will have to guess and test in order to find it. Once it's identified, you then know how to select the rows to be added to your combobox.
When I run this code it is also retrieving some other fields which are not present in the table. How can I overcome this?
Dim conn As New OleDb.OleDbConnection
'Create a connection string for an Access database
Dim strConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=F:\check\a.mdb"
'Attach the connection string to the connection object
conn.ConnectionString = strConnectionString
'Open the connection
conn.Open()
Dim Restrictions() As String = {Nothing, Nothing, selected, Nothing}
Dim CollectionName As String = "Columns"
Dim dt As DataTable = conn.GetSchema(CollectionName, Restrictions)
For Each TableRow As DataRow In dt.Rows
ComboBox1.Items.Add(TableRow.Item("COLUMN_NAME"))
The additional columns which is also retrieved are:
1.ID
2.Date create
3.Date update
4.Id
5.Lv
6.Name
7.parent Id
8.Type
9.GUID
10.Id
... and 6 more. The original schema consists of only 5 fields.
Your problem is simply that the variable selected has the value Nothing when you insert it into the Restrictions() array. When I run the following code I only get the column names for the table named [new]:
Option Strict On
Imports System.Data.OleDb
Module Module1
Sub Main()
Dim connStr As String =
"Provider=Microsoft.ACE.OLEDB.12.0;" &
"Data Source=C:\Users\Public\test\so34490626\a.mdb"
Using conn As New OleDbConnection(connStr)
conn.Open()
Dim selected As String = "new"
Dim Restrictions() As String = {Nothing, Nothing, selected, Nothing}
Dim CollectionName As String = "Columns"
Dim dt As DataTable = conn.GetSchema(CollectionName, Restrictions)
For Each TableRow As DataRow In dt.Rows
Console.WriteLine(TableRow.Item("COLUMN_NAME"))
Next
End Using
End Sub
End Module
The result is:
GENDER
MEMBER OF RISHI PRASAD
NAME OF SADHAK
PROFESSION
However, when the value of selected is Nothing ...
Dim selected As String = Nothing
... I get the "extra" columns you describe
DateCreate
DateUpdate
Id
Lv
Name
ParentId
Type
Attributes
DataType
FieldName
IndexType
SkipColumn
SpecID
Start
Width
DateDelim
DateFourDigitYear
DateLeadingZeros
DateOrder
DecimalPoint
FieldSeparator
FileType
SpecID
SpecName
SpecType
StartRow
TextDelim
TimeDelim
GENDER
MEMBER OF RISHI PRASAD
NAME OF SADHAK
PROFESSION
The reason is revealed if I change the Console.WriteLine to include the table names:
Console.WriteLine("[{0}].[{1}]", TableRow.Item("TABLE_NAME"), TableRow.Item("COLUMN_NAME"))
Then we see:
[MSysAccessStorage].[DateCreate]
[MSysAccessStorage].[DateUpdate]
[MSysAccessStorage].[Id]
[MSysAccessStorage].[Lv]
[MSysAccessStorage].[Name]
[MSysAccessStorage].[ParentId]
[MSysAccessStorage].[Type]
[MSysIMEXColumns].[Attributes]
[MSysIMEXColumns].[DataType]
[MSysIMEXColumns].[FieldName]
[MSysIMEXColumns].[IndexType]
[MSysIMEXColumns].[SkipColumn]
[MSysIMEXColumns].[SpecID]
[MSysIMEXColumns].[Start]
[MSysIMEXColumns].[Width]
[MSysIMEXSpecs].[DateDelim]
[MSysIMEXSpecs].[DateFourDigitYear]
[MSysIMEXSpecs].[DateLeadingZeros]
[MSysIMEXSpecs].[DateOrder]
[MSysIMEXSpecs].[DecimalPoint]
[MSysIMEXSpecs].[FieldSeparator]
[MSysIMEXSpecs].[FileType]
[MSysIMEXSpecs].[SpecID]
[MSysIMEXSpecs].[SpecName]
[MSysIMEXSpecs].[SpecType]
[MSysIMEXSpecs].[StartRow]
[MSysIMEXSpecs].[TextDelim]
[MSysIMEXSpecs].[TimeDelim]
[new].[GENDER]
[new].[MEMBER OF RISHI PRASAD]
[new].[NAME OF SADHAK]
[new].[PROFESSION]
The "MSys*" tables are system tables that are hidden by default in the Access user interface.
Before looping on your table rows, you need to identify the valid/permanent columns of your dataTable.
To do so, you should first browse the columns collection of your datatable object. By checking the properties of each one of these columns, you will be able to identify the temporary ones. I guess it might be hidden somewhere in the 'extended properties' of the DataColumn object.
In order to identify the right property, you will go through something like this (written on the fly ...):
For each tableColumn as DataColumn in dt.Columns
Console.WriteLine(tableColumn.[propertyName].ToString())
...
Next
I do not know exactly which one of the properties will let you know if the column is part of the original table fields. You will have to guess and test in order to find it. Once it's identified, you then know how to select the rows to be added to your combobox.
I know, how to increase integer, string by for statement
but I want know how to change the name by 'for ~next' statement.
For example,
Dim row_sort1 As DataTable = rows1.CopyToDataTable
Dim row_sort2 As DataTable = rows2.CopyToDataTable
Dim row_sort3 As DataTable = rows3.CopyToDataTable
Dim row_sort4 As DataTable = rows4.CopyToDataTable
Dim row_sort5 As DataTable = rows5.CopyToDataTable
Dim row_sort6 As DataTable = rows6.CopyToDataTable
Dim row_sort7 As DataTable = rows7.CopyToDataTable
I had coding like this,, bad cording So I want change by 'for ~next' statement.
I want increase the datatable name's number (1~7)
how can reflect in this coding. I want fix my coding more simple and useful.
I need your help
thank you
I assume you have a Collection of Rows which is your master where you want to copy from.
I have a similiar approach like OSKM. Better use a list collection than an array.
To access tables afterwards in the collection you can use Linq.
' Given master rowcollection
Dim masterRow As EnumerableRowCollection(Of DataRow)
' Empty table collection
Dim tableList As New List(Of DataTable)
For t As Integer = 0 To 6
' copy Master to a new table
Dim newTable As DataTable = masterRow.CopyToDataTable()
' give the new table a name
newTable.TableName = "Table" & t.ToString
' Add new table to collection
tableList.Add(newTable)
Next
' Access a certain table (i.e. Table5) using Linq
Dim table5 As DataTable = tableList.FirstOrDefault(Function(x) x.TableName = "Table5")
If i understand your question correct the following might help.
'Note your datatables will be named Datatable0 to Datatable6
Dim DTs(6) As DataTable
For i = 0 To 6
DTs(i) = New DataTable
DTs(i).TableName = "Datatable" & i
Next
There is probably better ways but this will work!
I'm doing .NET 3.5 programming in VB for a class. I have a .mdb database with 3 related tables, and a table adapter with some queries on it that look like this:
SELECT PropertyID, Street, Unit, City, Zip, Type, Bedrooms, Bathrooms, Area, MonthlyRent
FROM tblProperties
Then in a form i have a DataGridView. What i want to do is take the data that is returned from the query and display it in the DGV. However, when i do this, it displays all 35 columns in the database, not the 10 i selected (The ten are the only ones that have data in them however... so it's basically a table with a bunch of blank columns).
My current, inelegant solution is to return the query to a DataTable, then iterate through the table's columns, deleting the one's i dont want. This is not robust, efficient, and does not like me delete the primary key column.
My TA suggested trying to use an untyped databinding... he said this should display only the data I pull, but neither of us has been able to figure this out yet.
Thank You!
UPDATE
I'm not sure what you mean by the .aspx/.aspx.vb pages, but this is the query code i have from the table adapter
SELECT tblRent.PaymentID, tblTenant.TenantName, tblProperties.Street, tblProperties.Unit, tblProperties.City, tblRent.AmountPaid, tblRent.PaymentDate,
tblTenant.Telephone
FROM ((tblProperties INNER JOIN
tblRent ON tblProperties.PropertyID = tblRent.PropertyID) INNER JOIN
tblTenant ON tblProperties.PropertyID = tblTenant.PropertyID)
and here is where i use it in code:
Public Sub getRent()
propView.DataSource = TblPropertiesTableAdapter.GetAllRentReceipts()
propView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells)
propView.ReadOnly = True
End Sub
propView is a DataGridView that does not have a DataSource selected at load
I'm assuming that you're using Windows forms and a DataGridView with AutoGenerateColumns turned on.
If you add you own columns only the ones that you select will appear:
propView.AutoGenerateColumns = false
For Each //of the columns that you want
dim column as DataGridViewColumn = New DataGridViewColumn()
column.DataPropertyName = "DB field name"
column.HeaderText = "column title"
propView.Columns.Add( column )
Next
If you build the pages using tags, you'll want the following code in the page...
<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1">
</asp:GridView>
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings:MY_ConnectionString %>"
SelectCommand="SELECT tblRent.PaymentID, tblTenant.TenantName, tblProperties.Street, tblProperties.Unit, tblProperties.City, tblRent.AmountPaid, tblRent.PaymentDate, tblTenant.TelephoneFROM ((tblProperties INNER JOIN tblRent ON tblProperties.PropertyID = tblRent.PropertyID) INNER JOIN tblTenant ON tblProperties.PropertyID = tblTenant.PropertyID)"></asp:SqlDataSource>
If alternatively you want to use code for the data layer, you could use something along the lines of the following...
Public Class DataLayer
Public Function GetData(ByVal query As String, ByVal params As System.Data.Common.DbParameter()) As System.Data.DataTable
Dim dt As New System.Data.DataTable
Dim constr As String = System.Configuration.ConfigurationManager.ConnectionStrings("constr").ConnectionString()
Using cnObject As New System.Data.SqlClient.SqlConnection(constr)
Using cmd As New System.Data.SqlClient.SqlCommand(query, cnObject)
If Not params Is Nothing Then
For Each param In params
cmd.Parameters.Add(param)
Next
End If
Using da As New System.Data.SqlClient.SqlDataAdapter(cmd)
da.Fill(dt)
Return dt
End Using
End Using
End Using
End Function
End Class
If you are using OLEDB connection, this function can be changed as follows (meaning you only have to change one function to update every use of it in the application - nice)
Public Function GetDataOLE(ByVal query As String, ByVal params As System.Data.Common.DbParameter()) As System.Data.DataTable
Dim dt As New System.Data.DataTable
Dim constr As String = System.Configuration.ConfigurationManager.ConnectionStrings("constr").ConnectionString()
Using cnObject As New System.Data.OleDb.OleDbConnection(constr)
Using cmd As New System.Data.OleDb.OleDbCommand(query, cnObject)
If Not params Is Nothing Then
For Each param In params
cmd.Parameters.Add(param)
Next
End If
Using da As New System.Data.OleDb.OleDbDataAdapter(cmd)
da.Fill(dt)
Return dt
End Using
End Using
End Using
End Function
This function works is a generic data layer that will take in any SQL Command and output the data in a DataTable, which can simply be bound to a grid view or similar. You can check that the "query" is only selecting the columns you want but debugging the code and checking the SQL command to your database.
I would build it into a class so that any page could access it to load data into a data table.
Your code would become
Public Sub getRent()
Dim dataLayer As New DataLayer()
Dim sqlText As String = "<insert your query text here>"
propView.DataSource = dataLayer.getData(sqlText, Nothing)
propView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells)
propView.ReadOnly = True
End Sub