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
Related
I working with List(Of) in VB.NET. I would like to "sort" a list by a key/unique field (FEIN), but get an error. I created the list with three columns (FEIN, Status, Term_Date). This list loads fine. Additionally, I would like to "search" this list in a quick and efficient way (BinarySearch?). I attempted using FindIndex, but it is very slow. I realize my sort/search syntax assumes a single column list, but I'm trying to get this to work with a multi-column list. I attached my code with comments and errors. I'm working in Visual Studio 2019, VB.NET
'define columns in list
Public Class AddInfo
'custom class additional info
Public Property FEIN As String
Public Property Status As String
Public Property Term_Date As String
Public Sub New(ByVal FEIN As String,
ByVal Status As String,
ByVal Term_Date As String)
Me.FEIN = FEIN
Me.Status = Status
Me.Term_Date = Term_Date
End Sub
End Class
'populate list (3 columns) from source datatable (dtAddInfo), this list populates nicely
Dim lstAddInfo = New List(Of AddInfo)
For Each r As DataRow In dtAddInfo.Rows
lstAddInfo.Add(New AddInfo(r("FEIN"), r("Status"), r("Term_Date")))
Next
'sort newly populated list by FEIN
lstAddInfo.Sort() 'this throws "Failed to compare two elements in the array."
'find index/row in list using FEIN
Dim index = lstAddInfo.BinarySearch("123")) 'this throws "Unable to cast object of type 'System.String' to type"
'NOTE: I tried using the following instead of BinarySearch. It works, but is painfully slow (list contains 220K rows)
Dim index = lstAddInfo.FindIndex(Function(x) x.FEIN = "123")) 'search for FEIN in list
Unfortunately, I cannot reduce the number of rows from the source database table. But...I was able to track down another alternative. I used a LINQ query to join the two datatables (Excel and AddInfo) to create a new, pared-down target datatable based on FEIN. This seems to speed things up significantly. I attached the code for building a joined datatable. Thanks again.
'use LINQ query to join Excel and Add Info datatables on FEIN
Dim joinedResults =
From Excl In dt_Excel.AsEnumerable()
Join AddInfo In dt_Add_Info.AsEnumerable()
On Excl.Field(Of String)("FEIN") Equals AddInfo.Field(Of String)("FEIN")
Select
FEIN = Excl.Field(Of String)("FEIN"),
Status = AddInfo.Field(Of String)("Status"),
Term_Date = AddInfo.Field(Of String)("Term_Date")
'define target datatable
dt_Add_Info = New DataTable
dt_Add_Info.Columns.Add("FEIN", GetType(String))
dt_Add_Info.Columns.Add("Status", GetType(String))
dt_Add_Info.Columns.Add("Term_Date", GetType(String))
'populate target datatable
For Each row In joinedResults
Dim dr As DataRow = dt_Add_Info.NewRow()
dr("FEIN") = row.FEIN
dr("Status") = row.Status
dr("Term_Date") = row.Term_Date
dt_Add_Info.Rows.Add(dr)
Next
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
I have an application that loads cars data from sql server database as follows.
sql = " select car.*, (model + ' -- ' + plateNo) as selectCar from car"
daAdapter = New SqlDataAdapter(sql, sqlConn)
daAdapter.Fill(dsDataset, "car")
So data would look like the following:
Then instead of loading distinct values of brand from database I have created one datatable to get the distinct values of the brand. And then add it to the dataset as follows:
tblBrand = New DataTabl
tblBrand = dsDataset.Tables("car").DefaultView.ToTable(True, "brand")
tblBrand.TableName = "brand"
dsDataset.Tables.Add(tblBrand)
Actually I use this brand table to display the filter options in the brand combobx without any problem.
What I need is that when I press the add button I go to an other form and I want to use this brand options in the other form. So when I click the add button I have the following code:
Dim newCar As New frmCar(dsDataset)
carMode = "new"
newCar.ShowDialog()
And I created a constructor in the frmCar form as follows:
Sub New(ByVal dsOptions)
InitializeComponent()
Try
cmbBrand.DataSource = dsOptions.table("brand")
cmbBrand.DisplayMember = "brand"
cmbBrand.ValueMember = "brand"
Catch ex As Exception
MsgBox(Err.Description)
End Try
End Sub
But the following error pops up:
Public member 'table' on type 'DataSet' not found.
What is wrong with my code?
The first thing you should do is to turn both option strict and explicit On. This will prevent you from doing these kinds of mistakes in the future.
The error is quite self-explainable. A DataSet doesn't have a public member (property, method etc..) named table.
All you have to do is change:
Sub New(ByVal dsOptions)
cmbBrand.DataSource = dsOptions.table("brand")
to:
Sub New(ByVal dsOptions As DataSet)
cmbBrand.DataSource = dsOptions.Tables("brand")
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.
I have these fields on Customer DataTable: ID,title,Name,Addrs,email,Fax and this code to bind the DataGridView:
Dim sql As String = "SELECT * FROM Customers"
Dim daHeader As New SqlDataAdapter(sql, Conn)
daHeader.Fill(dsNota, "Customers")
dgvHeader.DataSource = dsNota.Tables("Customers")
How do I view title,name,addrs data in DataGridView without changing the SQL string to:
"SELECT title,Name,Addrs FROM Customer"
So if you don't want to modify your query string (as #Neolisk noticed this is generally a bad practice to use Select * but this is another debat), and so you get more columns than what you want to display:
Solution1 (Ideal if there are a lot of columns in datatable and you want to display just some of them)
You need to set AutoGenerateColumns property to false.
Default is True, so DataGridView will create a column for all columns in the datatable.
Then, you add a DatagridiviewColumn for each column you want to display.
To avoid to have to add a celltemplate for the DatagriviewColumn (see this) you would prefer to add a strongly typed column (DataGridViewTextBoxColumn for example in order to display String values). In order to bind the column with the source, you set the DataPropertyName property, that needs to match with the ColumnName of the column inDataTable.
So code would be:
dgvheader.AutoGenerateColumns = False
dgvHeader.Columns.Add(New DataGridViewTextBoxColumn() With {.HeaderText = "Title", .DataPropertyName = "title"})
dgvHeader.Columns.Add(New DataGridViewTextBoxColumn() With {.HeaderText = "Name", .DataPropertyName = "Name"})
dgvHeader.Columns.Add(New DataGridViewTextBoxColumn() With {.HeaderText = "Adresse", .DataPropertyName = "Addrs"})
dgvHeader.DataSource = dsNota.Tables("Customers")
Solution2 (Ideal if there are a lot of columns in datatable, and you want to Hide just some of them, and so you want to keep the benefit of AutoGenerateColumns)
Set AutoGenerateColumns property to true (or do nothing since
default is True)
Hook the DataGridView.DataBindingComplete Event in order to hide some columns autogenerated:
Private Sub dataGridView1_DataBindingComplete(ByVal sender As Object, ByVal e As DataGridViewBindingCompleteEventArgs) Handles dgvHeader.DataBindingComplete
With dgvHeader
.Columns("Fax").Visible = False
End With
End Sub
If the gridview autogeneratecolumns attribute is set to true change it to false and then do as Raimond suggested. Example:
<asp:GridView ID="gvSearchResults" runat="server" AutoGenerateColumns="False">
<Columns>
<asp:BoundField DataField="title" HeaderText="Title" />
</Columns>
</asp:GridView>
I know this post goes way back, but I had the same problem in C# and this is how I solved it which works really well.
1 - Build a DataSet with your SQL query
private void LoadDS()
{
// this method gets data from my database
// DS is a DataSet in the properties of my form
DS = LoadData();
}
2 - Get the DataView filtered the way you want
For this I use the RowFilter property of the DataView.
I created a GetFiltersToString() method that formats every filter control I have in a string that matches the RowFilter syntax (more information about the syntax here, and msdn definition of RowFilter here)
public void RefreshDGV()
{
// Get the corresponding dataview
DV = new DataView(DS.Tables[0], rowFilter, "SORTINGCOLUMN Desc", DataViewRowState.CurrentRows);
// Display it in a datagridview
DGV.DataSource = DV;
}
I find that this solution allows user to change the filtering much more easily.
You have to explicitly add the columns to your grid.