I have a Dynamic Data LINQ to SQL ASP.Net Website in VB.NET, and am having a little trouble with Sorting of my GridView and a Search routine I have implemented. On Page_Load, the GridView is sorted by a field (Departments.department) in ASC order. However, when I perform a search using the code below, I get an error
The Data Source Does Not Support Sorting.
I'm assuming the problem comes when the Page_Load event tries to sort the data after a Search is made, because of the DataSource/ID.
Dim button = DirectCast(sender, Button)
If button.ID = btnMultiColumnSearchClear.ID Then
txbMultiColumnSearch.Text = [String].Empty
Else
Using Data As New wcPhonesDataContext()
Dim EmployeeNameString As String = txbMultiColumnSearch.Text
Dim SearchResults = Data.Employees.Where(Function(Employees) Employees.Employee.Contains(EmployeeNameString))
GridView1.DataSourceID = ""
GridView1.DataSource = SearchResults
GridView1.DataBind()
End Using
End If
SOLVED, but now I have a new problem, here is the code I used to solve this issue...
Dim button = DirectCast(sender, Button)
If button.ID = btnMultiColumnSearchClear.ID Then
txbMultiColumnSearch.Text = [String].Empty
Else
Using Data As New wcPhonesDataContext()
Dim EmployeeNameString As String = txbMultiColumnSearch.Text
Dim SearchResults = Data.Employees.Where(Function(Employees) Employees.Employee.Contains(EmployeeNameString))
GridView1.Sort("", SortDirection.Ascending)
GridView1.DataSourceID = ""
GridView1.DataSource = SearchResults
GridView1.DataBind()
End Using
End If
I have created a new error though. It occurs if I perform a second search without going BACK to the Employees table.
'GridView1' fired event Sorting which wasn't handled.
If you use a SqlDataSource and connect the gridview to the data source with a datasourceid, then sorting is done for you magically. You don't have to do anything to support it.
But if set the datasource to some object that you have created in code, sorting does NOT magically happen for you. When the user clicks on a column head, this fires an OnSorting event. You have to write code to handle the event. Typically this mean regenerating the data in the desired order, or regenerating the data and then sorting it.
For example, if you generate the data with a SQL query, I sometimes create a function that runs the SQL query and returns a DataSet. This function takes the sort field as a parameter, which it pastes into the SQL query. Then for the initial display call this function passing in the default sort order, and for the OnSorting call this function passing in the desired sort field.
Related
so I'm using SQLite in a VB.net project with a populated database. I'm using the Microsoft.Data.Sqlite.Core and System.Data.SQLite NuGet package libraries. So the problem presents when I'm trying to get the result of a query. At first the SQLiteDataReader gets the response and all the elements of the desired table. I know this cause in the debugger I have a breakpoint after the setting the object and when I check the parameters of the SQLiteDataReader object the Result View shows all the elements of my table, but as soon as I remove the mouse from the object and check it again the Result View turns out empty, without even resuming with the next line of code. Does anyone know if its a known bug or something cause Ive used this exact method of querying a table in another project and it works.
The code:
Public Function RunQuery(com As String)
If CheckConnection() Then
command.CommandText = com
Dim response As SQLiteDataReader
response = command.ExecuteReader
Dim len As Integer = response.StepCount
Dim col As Integer = response.FieldCount
Dim resp(len, col) As Object
For i = 0 To col - 1
Using response
response.Read()
For j = 0 To len - 1
resp(i, j) = response.GetValue(j)
Next
End Using
Next
Debugger with populated result view
Debugger with empty result view
edit: added the for loop to show that its not only on the debugger that the result view is empty. When using response.Read() it throws an exception "System.InvalidOperationException: 'No current row'"
As I have told you in the comment, a DataReader derived class is a forward only retrieval object. This means that when you reach the end of the records returned by the query, that object is not capable to restart from the beginning.
So if you force the debugger to enumerate the view the reader reaches the end of the results and a second attempt to show the content of the reader fails.
The other part of your problem is caused by a misunderstanding on how to work on the reader. You should loop over the Read result not using a StepCount property. This property as far as I know, is not standard and other data providers don't support it. Probably it is present in the SQLite Provider because for them it is relatively easy to count the number of records while other providers don't attempt do calculate that value for performance reasons.
However, there are other ways to read from the reader. One of them is to fill a DataTable object with its Load method that conveniently take a DataReader
Dim data As DataTable = New DataTable()
Using response
data.Load(response)
End Using
' Now you have a datatable filled with your data.
' No need to have a dedicated bidimensional array
A DataTable is like an array where you have Rows and Columns instead of indexes to iterate over.
I am quite new to LINQ but some help from a field has produced the following code that works OK; but does not write the data to the SQL Server database records.
Public Function Error_Log_Add(ServiceDateRec As VariantType, Seq As VariantType, RosterID As VariantType, ContactID As VariantType, MessageID As VariantType)
'
' Add Error Message to a Service
'
Error_Log_Add = False
Dim context As RosterMaster_Rostering = Me.RosterMaster_Rostering
Dim newErrorDataSet As RosterMaster_Rostering.ErrorLogDataTable = context.ErrorLog
Dim newErrorLogRow As RosterMaster_Rostering.ErrorLogRow
newErrorLogRow = newErrorDataSet.NewRow()
newErrorLogRow.ServiceDateID = ServiceDateRec
newErrorLogRow.Seq = Seq
newErrorLogRow.Roster_Required = RosterID
newErrorLogRow.PostID = ContactID
newErrorLogRow.Error_Message = MessageID
newErrorDataSet.Rows.Add(newErrorLogRow)
newErrorDataSet.AcceptChanges()
MsgBox("Inserted" + newErrorDataSet.Rows.Count.ToString())
End Function
Can someone point me in the right direction please.
You're so new to LINQ that you haven't even started with it apparently, because there is no LINQ at all in that code. You are using a typed DataSet, which is simply ADO.NET.
The problem is that you're not actually trying to save anything to the database. You need to call Update on a table adapter for that to happen, which is nowhere to be seen in that code. You seem to be under the impression that calling AcceptChanges will save something but it won't. The DataRows in your DataTable each track their own changes and AcceptChanges basically tells them to stop doing so because those changes have been saved. You haven't actually done the saving though. You need to call Update on the appropriate table adapter and it will save the changes and then implicitly call AcceptChanges.
After going over multiple questions/answers on Stackoverflow and other boards I'm still lost on why I can't update an Access database from a datatable. I'm trying to take data from a datatable and insert that data into an Access table if it is blank, and replace the table if it already has data. I can successfully replace the table, but the data from the datatable does not get added.
However, the method which I'm using does not appear to work. My datatable comes from a bound datagridsource and the Access layer is called like this:
ConnectedDB.UpdateTable(DBTable, bsDataSource.DataSource)
Where ConnectedDB is the Access Layer class, DBTable is the string containing the Access table name, and bsDataSource is the bound data. As you can see, I passed the .Datasource to turn it into a datatable.
Here is the original (pre-Jan 29th) section of my work to add the datatable back into the Access table:
Public Function UpdateTable(strTable As String, dgDataTable As DataTable) As Boolean
Dim DS As New DataSet
dgDataTable.TableName = strTable
DS.Tables.Add(dgDataTable)
Using OpenCon = New OleDb.OleDbConnection(strConnectionString)
Using DataAdapter As New OleDbDataAdapter("SELECT * FROM " & strTable, OpenCon)
Dim DBcmd As OleDbCommandBuilder = New OleDbCommandBuilder(DataAdapter)
DBcmd.QuotePrefix = "["
DBcmd.QuoteSuffix = "]"
DataAdapter.UpdateCommand = DBcmd.GetUpdateCommand()
Try
OpenCon.Open()
DataAdapter.Fill(DS.Tables(strTable))
If DataAdapter.Update(DS.Tables(strTable)) > 0 Then
Return True
Else Return False
End If
Catch exo As Exception
MessageBox.Show(exo.Message)
Return False
End Try
End Using
End Using
End Function
My function tries to update an existing Access table with the name represented as strTable with the information in the datatable, dgDataTable from a datagridview. Each run hits the update check > 0 and returns a false which means syntax wise it should be working (i.e. no error messages). I have traced the table and it has all the data it should have (so the information is getting passed correctly from the grid through the update commands). I was playing with applying it in a dataset but I'm not sure I really need that.
I was tracing the variables through the update method and I think I found out why it won't update but I'm not sure what to do about it. The query it comes up with is like this:
UPDATE [RtoC] SET [R] = ?, [C] = ?, [Type] = ?, [Unknown] = ? WHERE (([R] = ?) AND ([C] = ?) AND ([Type] = ?) AND ((? = 1 AND [Unknown] IS NULL) OR ([Unknown] = ?)))
The Access table name is RtoC with fields R, C, Type, and unknown.
I'm thinking the "?" are not getting filled in causing the query to just not apply data back to Access. I'm not sure though how to set those items.
EDIT 1/29/20: I used the code changes I and jmcihinney document below and it does insert the lines into the Access table. This edit alters the question to be more specific about what I'm am trying to do, and how the datatable is created. Hopefully this clears up some wording on my part and provides some basis for the alteration of the row state.
The issue is that the Fill method of that data adapter calls AcceptChanges on the DataTable after populating it, thus there are no changes to save when you call Update.
That call to Fill shouldn't be there anyway though, because you don't want to retrieve any data, just save changes. You've got a whole lot of pointless code there. It should look more like this:
Public Function UpdateTable(strTable As String, dgDataTable As DataTable) As Boolean
Using DataAdapter As New OleDbDataAdapter("SELECT * FROM " & strTable, strConnectionString)
Dim DBcmd As OleDbCommandBuilder = New OleDbCommandBuilder(DataAdapter)
DBcmd.QuotePrefix = "["
DBcmd.QuoteSuffix = "]"
Try
Return DataAdapter.Update(dgDataTable) > 0
Catch exo As Exception
MessageBox.Show(exo.Message)
Return False
End Try
End Using
End Function
I took another tack at manipulating the database and in looking that up, I found the answer provided by jmcilhinney back in 2014! [Bulk Insert From DataTable to Access Database
In a for each loop across the rows of my datatable I set this:
row.SetAdded()
If I was filling I would have done something like:
DataAdapter.AcceptChangesDuringFill = True
Before the Fill command.
Unless this method has changed or there is a better way, I'll mark the link as the answer.
Thanks jmcilhinney....twice!
For each connection in an array called ALLconn, I would like to compare it to my sql table. If exist, then add to my listview. Here is my code below, but it does not seem to work:
Dim LoginFilter As Object
Dim SelCurrAllSessions As SqlClient.SqlCommand = New SqlClient.SqlCommand("Select * from CurrAllSessions", LFcnn)
SelCurrAllSessions.CommandType = CommandType.Text
LoginFilter = SelCurrAllSessions.ExecuteReader
For Each conn In AllConn
While LoginFilter.Read()
If conn.UserName.ToString() = LoginFilter.Item(0) Then
ListBox1.Items.Add(LoginFilter.Item(0))
End If
End While
Next
Well you need to change the order of the loops
While LoginFilter.Read()
For Each conn In AllConn
If conn.UserName.ToString() = LoginFilter.Item(0).ToString Then
ListBox1.Items.Add(LoginFilter.Item(0).ToString)
Exit For
End If
Next
End While
This is necessary because in your original code, the internal while run till the end of the data loaded from the database then, when you try to check the next conn, you cannot reposition the reader at the start of the data loaded by the database.
It's the other way around, use Contains to check if the string is contained in the collection, then you can add it to the ListBox:
Using LoginFilter = SelCurrAllSessions.ExecuteReader()
While LoginFilter.Read()
Dim connection = LoginFilter.GetString(0)
If AllConn.Contains(connection) Then
ListBox1.Items.Add(connection)
End If
End While
End Using
Actually, your Question is uncompleted but still as per my understanding your trying to read result of SqlCommand.ExecuteReader(). When you read that Result it will reading from first to last and that is only one time you can read. if you try to read again it will show up an error because no more content to read from an object Loginfilter.
So, Either you can store that reading into an array and continue with your Foreach and while logic with newly created array or you can do reading from LoginFilter and compare into forech connections in AllConn array or List.
Please feel free to response me if you need more explaination, I will send you in C# version.
Update
I decided to iterate through the Data.DataTable and trimmed the values there.
Utilizing SirDemon's post, I have updated the code a little bit:
Sub test(ByVal path As String)
Dim oData As GSDataObject = GetDataObj(path)
EmptyComboBoxes()
Dim oDT As New Data.DataTable
Try
Dim t = From r In oData.GetTable(String.Format("SELECT * FROM {0}gsobj\paths ORDER BY keyid", AddBS(path))) Select r
If t.Count > 0 Then
oDT = t.CopyToDataTable
For Each dr As Data.DataRow In oDT.Rows
dr.Item("key_code") = dr.Item("key_code").ToString.Trim
dr.Item("descript") = dr.Item("descript").ToString.Trim
Next
dataPathComboBox.DataSource = oDT
dataPathComboBox.DisplayMember = "descript"
dataPathComboBox.ValueMember = "key_code"
dataPathComboBox.SelectedIndex = 0
dataPathComboBox.Enabled = True
End If
Catch ex As Exception
End Try
End Sub
This works almost as I need it to, the data is originally from a foxpro table, so the strings it returns are <value> plus (<Field>.maxlength-<value>.length) of trailing whitespace characters. For example, a field with a 12 character length has a value of bob. When I query the database, I get "bob_________", where _ is a space.
I have tried a couple of different things to get rid of the whitespace such as:
dataPathComboBox.DisplayMember.Trim()
dataPathComboBox.DisplayMember = "descript".Trim.
But nothing has worked yet. Other than iterating through the Data.DataTable or creating a custom CopyToDataTable method, is there any way I can trim the values? Perhaps it can be done in-line with the LINQ query?
Here is the code I have so far, I have no problem querying the database and getting the information, but I cannot figure out how to display the proper text in the ComboBox list. I always get System.Data.DataRow :
Try
Dim t = From r In oData.GetTable("SELECT * FROM ../gsobj/paths ORDER BY keyid") _
Select r
dataPathComboBox.DataSource = t.ToList
dataPathComboBox.SelectedIndex = 0
'dataPathComboBox.DisplayMember = t.ToList.First.Item("descript")
dataPathComboBox.Enabled = True
Catch ex As Exception
Stop
End Try
I know that on the DisplayMember line the .First.Item() part is wrong, I just wanted to show what row I am trying to designate as the DisplayMember.
I'm pretty sure your code tries to set an entire DataRow to a property that is simply the name of the Field (in a strongly type class) or a Column (in a DataTable).
dataPathComboBox.DisplayMember = "descript"
Should work if the DataTable contains a retrieved column of that name.
Also, I'd suggest setting your SelectedIndex only AFTER you've done the DataBinding and you know you actually have items, otherwise SelectedIndex = 0 may throw an exception.
EDIT: Trimming the name of the bound column will trim just that, not the actual bound value string. You either have to go through all the items after they've been bound and do something like:
dataPathComboBox.Item[i].Text = dataPathComboBox.Item[i].Text.Trim()
For each one of the items. Not sure what ComboBox control you're using, so the item collection name might be something else.
Another solution is doing that for each item when it is bound if the ComboBox control exposes an onItemDataBound event of some kind.
There are plenty of other ways to do this, depending on what the control itself offers and what you choose to do.
DisplayMember is intended to indicate the name of the property holding the value to be displayed.
In your case, I'm not sure what the syntax will by since you seem to be using a DataSet, but that should be
... DisplayMember="Item['descript']" ...
in Xaml, unless you need to switch that at runtime in which case you can do it in code with
dataPathComboBox.DisplayMember = "Item['descript']"
Again, not 100% sure on the syntax. If you are using a strongly typed DataSet it's even easier since you should have a "descript" property on your row, but given hat your error indicates "System.DataRow" and not a custom type, I guess you are not.
Because I can't figure out the underlying type of the datasource you are using I suggest you to change commented string to
dataPathComboBox.DisplayMember = t.ElementType.GetProperties.GetValue(0).Name
and try to determine correct index (initially it is zero) in practice.