Bound datatgridview not saving changes to tableadapter, and thus not to database - vb.net

I have a datagridview bound to a table that seems to work fine, but will not save changes to the database. I am using the same code as I used in another project, which did save changes, so I am flummoxed. Also, I have debugged and the save code is being called, just not working. The code in the form calls a separate class with business logic.
Code in the form below:
Private Sub frmAdminAssign_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' Below is generated by Visual Studio when I select my data source for my datagridview
Credit_adminTableAdapter.Fill(DataSet1.credit_admin) ' Foreign key relationship to table being updated (lookup table).
LoanTableAdapter.Fill(DataSet1.loan) ' Table being updated
admin_assign = New AdminAssign()
admin_assign.FilterUnassigned(dgvAssign, LoanTableAdapter)
End Sub
Private Sub dgvAssign_CellEndEdit(sender As Object, e As DataGridViewCellEventArgs) Handles dgvAssign.CellEndEdit
admin_assign.SaveAssignmentChanges(DataSet1, LoanTableAdapter)
End Sub
Below is code in business logic class called from above:
Public Sub SaveAssignmentChanges(ByRef data_set As DataSet1, ByRef loan_table_adapter As DataSet1TableAdapters.loanTableAdapter)
' Saves changes to DB.
Dim cmdBuilder As SqlCommandBuilder
cmdBuilder = New SqlCommandBuilder(loan_table_adapter.Adapter)
loan_table_adapter.Adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(True)
Try
loan_table_adapter.Adapter.Update(data_set)
Catch unknown_ex As Exception
Dim error_title As String = "Database Save Error"
Dim unknown_error As String = $"There was an error saving to the database.{vbNewLine}Error: {unknown_ex.ToString}"
MessageBox.Show(unknown_error, error_title, MessageBoxButtons.OK, MessageBoxIcon.Warning)
End Try
End Sub
The data from the datagridview is not saving to the tableadapter, which I found out by adding the lines below in the start of the second procedure:
Dim x As String = loan_table_adapter.GetData()(0)("note_number") ' Unchanged, check was right row
Dim y As String = loan_table_adapter.GetData()(0)("credit_admin_id") ' Changed in datagridview but still null (get null error)

I figured it out, it was related to a filter routine that is not above where I passed a tableadapter byref when I should have passed a datatable byval. So, related to one of jmcilhinney's byref vs byval comments above. See problem code below:
Public Sub FilterUnassigned(ByRef dgv_assigned As DataGridView, loan_table_adapter As DataSet1TableAdapters.loanTableAdapter)
' Should have passed in DataSet1.loan DataTable ByVal and used it below:
Dim unassigned_loans As DataView = loan_table_adapter.GetData().DefaultView()
unassigned_loans.RowFilter = "request_date Is Not Null AND numerated_assigned_user Is Null"
dgv_assigned.DataSource = unassigned_loans
End Sub
Also, skipping the SQLCommandBuilder commands and my code still works. So, all I need is one line as per jmcilhinney instead of 4 (excl error handling) as per below:
Try
loan_table_adapter.Update(data_set)
Catch unknown_ex As Exception
Dim error_title As String = "Database Save Error"
Dim unknown_error As String = $"There was an error saving to the database. Please contact Kevin Strickler to resolve.{vbNewLine}Error: {unknown_ex.ToString}"
MessageBox.Show(unknown_error, error_title, MessageBoxButtons.OK, MessageBoxIcon.Warning)
End Try

Related

VB.NET DataSet table data empty

I'm trying to use the dataset for a report, but the data is gone when I try to use it. Here is my code for the most part:
Variables:
Dim ResultsDataView As DataView
Dim ResultsDataSet As New DataSet
Dim ResultsTable As New DataTable
Dim SQLQuery As String
Search:
This is where a datagrid is populated in the main view. The data shows up perfectly.
Private Sub Search(Optional ByVal Bind As Boolean = True, Optional ByVal SearchType As String = "", Optional ByVal SearchButton As String = "")
Dim SQLQuery As String
Dim ResultsDataSet
Dim LabelText As String
Dim MultiBudgetCenter As Integer = 0
SQLQuery = "A long and detailed SQL query that grabs N rows with 7 columns"
ResultsDataSet = RunQuery(SQLQuery)
ResultsTable = ResultsDataSet.Tables(0)
For Each row As DataRow In ResultsTable.Rows
For Each item In row.ItemArray
sb.Append(item.ToString + ","c)
Response.Write(item.ToString + "\n")
Response.Write(vbNewLine)
Next
sb.Append(vbCr & vbLf)
Next
'Response.End()
If Bind Then
BindData(ResultsDataSet)
End If
End Sub
Binding Data:
I think this is a cause in the issue.
Private Sub BindData(ByVal InputDataSet As DataSet)
ResultsDataView = InputDataSet.Tables("Results").DefaultView
ResultsDataView.Sort = ViewState("SortExpression").ToString()
ResultsGridView.DataSource = ResultsDataView
ResultsGridView.DataBind()
End Sub
Reporting action:
This is where I am trying to use the table data. But it is showing as nothing.
Protected Sub ReportButton_Click(sender As Object, e As EventArgs) Handles ReportButton.Click
For Each row As DataRow In ResultsTable.Rows
For Each item In row.ItemArray
Response.Write(item.ToString)
Next
Next
End Sub
The reason I'm trying to loop through this data is to both display the data in a gridview on the main view as well as export the data to CSV. If there is a different way to export a SQL query to CSV, I'm open to any suggestions.
There has to be something I can do to get the data from the SQL query to persist through the ReportButton_Click method. I've tried copying the datatable, I've tried global variables, I've tried different methods of looping through the dataset. What am I missing?!
Thank you all in advance.
EDIT
Here is the Page_Load:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Page.IsPostBack Then
'set focus to postback control
Dim x As String = GetPostBackControlName(Page)
If Len(x) > 0 Then
x = x.Replace("$", "_")
SetFocus(x)
End If
End If
If Not IsPostBack Then
ResultsGridView.AllowPaging = False
'Enable Gridview sorting
ResultsGridView.AllowSorting = True
'Initialize the sorting expression
ViewState("SortExpression") = "ID DESC"
'Populate the Gridview
Search()
End If
End Sub
In your search function add this line after the ResultsTable setting
ResultsTable = ResultsDataSet.Tables(0)
Session("LastSearch") = ResultsTable
Then in your report click event handler recover your data from the Session variable
Protected Sub ReportButton_Click(sender As Object, e As EventArgs) Handles ReportButton.Click
ResultsTable = DirectCast(Session("LastSearch"), DataTable)
For Each row As DataRow In ResultsTable.Rows
For Each item In row.ItemArray
Response.Write(item.ToString)
Next
Next
End Sub
You need to read about ASP.NET Life Cycle and understand that every time ASP.NET calls your methods it creates a new instance of your Page class. Of course this means that global page variables in ASP.NET are not very useful.
Also consider to read about that Session object and not misuse it.
What is the difference between SessionState and ViewState?

How do I add a new row to a binding source

I am trying to programmatically add a new row to a binding source . I know calling the bsSource.AddNew() adds a new row which I cast as a DataRowView and I set my values. My problem is this - the DataRowView.Row shows a RowState of detached. I do not want it to be detached ; I believe it should show added - I also do NOT want it to commit the change to the database (There is a very valid reason for this). I want to pick the time for that later.
My code is as follows:
private Sub AddToRelationSource(binID As Integer, gradeID As Integer, IsChecked As Boolean)
Dim drv As DataRowView = DirectCast(bsBinGrades.AddNew(), DataRowView)
drv.Row("IsSelected") = IsChecked
drv.Row("BinID") = binID
drv.Row("GradeID") = gradeID
' I tried drv.EmdEdit(0 drv.Row.EndEdit() - Row State still shows detached
End Sub
The BindingSource AddNew method does not actually add a new record to the underlying datasource , it simply adds it to the bindingsource as a detached item. When using the datatabel as a datasource I needed to get the datatable and use the AddRow() method - this properly set the value in my bindingsource to added so that when the changes would be committed to the database on bindingsource.Update() method.
The code I used:
Dim drv As DataRowView = DirectCast(bsData.AddNew(), DataRowView)
drv.BeginEdit()
drv.Row.BeginEdit()
drv.Row("IsSelected") = IsChecked
drv.Row.EndEdit()
drv.DataView.Table.Rows.Add(drv.Row)
The last line is what actually added the item to the datasource - I misunderstood BindingSource.AddNew() .
The following may be in the right direction. First I used a few language extension methods e.g.
Public Module BindingSourceExtensions
<Runtime.CompilerServices.Extension()>
Public Function DataTable(ByVal sender As BindingSource) As DataTable
Return CType(sender.DataSource, DataTable)
End Function
<Runtime.CompilerServices.Extension()>
Public Sub AddCustomer(ByVal sender As BindingSource, ByVal FirstName As String, ByVal LastName As String)
sender.DataTable.Rows.Add(New Object() {Nothing, FirstName, LastName})
End Sub
<Runtime.CompilerServices.Extension()>
Public Function DetachedTable(ByVal sender As BindingSource) As DataTable
Return CType(sender.DataSource, DataTable).GetChanges(DataRowState.Detached)
End Function
<Runtime.CompilerServices.Extension()>
Public Function AddedTable(ByVal sender As BindingSource) As DataTable
Return CType(sender.DataSource, DataTable).GetChanges(DataRowState.Added)
End Function
End Module
Now load ID, FirstName and LastName into a DataTable, Datatable becomes the DataSource of a BindingSource which is the BindingSource for a BindingNavigator and are wired up to a DataGridView.
Keeping things simple I mocked up data, has no assertions e.g. make sure we have valid first and last name, instead concentrate on the methods.
First use a extension method to add a row to the underlying DataTable of the BindingSource.
bsCustomers.AddCustomer("Karen", "Payne")
Now check to see if there are detached or added rows
Dim detachedTable As DataTable = bsCustomers.DetachedTable
If detachedTable IsNot Nothing Then
Console.WriteLine("Has detached")
Else
Console.WriteLine("No detached")
End If
Dim AddedTable As DataTable = bsCustomers.AddedTable
If AddedTable IsNot Nothing Then
Console.WriteLine("Has added")
Else
Console.WriteLine("None added")
End If
Since we are not talking to the database table, the primary key is not updated as expected and since you don't want to update the database table this is fine. Of course there is a method to get the primary key for newly added records if you desire later in your project.
Addition
Private Sub BindingSource1_AddingNew(ByVal sender As System.Object, ByVal e As System.ComponentModel.AddingNewEventArgs) Handles BindingSource1.AddingNew
Dim drv As DataRowView = DirectCast(BindingSource1.List, DataView).AddNew()
drv.Row.Item(0) = "some value"
e.NewObject = drv
' move to new record
'BindingSource1.MoveLast()
End Sub
'This routine takes the AddForm with the various fields that the user
'fills in and calls the TableAdapter's Insert method.
'After that is done, then the table has be be reflected back to the
'various components.
Private Sub AddRecord()
'The following line did not work because I could not get
'the bs definition down.
'Tried the BindingSource but in gave an error on
'DataRowView so I came up with an alternate way of
'adding the row.
'Dim drv As DataRowView = DirectCast(bsData.AddNew(), DataRowView)
'Dim drv As DataRowView = DirectCast(RecTableBindingSource.AddNew(), DataRowView)
'drv.BeginEdit()
'drv.Row.BeginEdit()
'drv.Row("Title") = "Order, The"
'drv.Row.EndEdit()
'drv.DataView.Table.Rows.Add(drv.Row)
RecTableTableAdapter.Insert(pAddForm.tTitle.Text,
pAddForm.tCast.Text,
pAddForm.tAKA.Text,
pAddForm.tRelated.Text,
pAddForm.tGenre.Text,
pAddForm.tRated.Text,
pAddForm.tRelease.Text,
pAddForm.tLength.Text)
Validate()
RecTableBindingSource.EndEdit()
RecTableTableAdapter.Update(VideoDBDataSet.RecTable)
RecTableAdapterManager.UpdateAll(VideoDBDataSet)
RecTableTableAdapter.Fill(VideoDBDataSet.RecTable)
VideoDBDataSet.AcceptChanges()
End Sub
'Here is my Delete Record routine
Private Sub DeleteRecordToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles DeleteRecordToolStripMenuItem.Click
Dim RowIndex As Int32
If (dgvRec.SelectedRows.Count > 0) Then
RowIndex = dgvRec.SelectedRows(0).Index
'Now we have to delete the record
dgvRec.Rows.RemoveAt(RowIndex)
dgvRec.CommitEdit(RowIndex)
dgvRec.EndEdit()
Validate()
RecTableBindingSource.EndEdit()
RecTableTableAdapter.Update(VideoDBDataSet.RecTable)
RecTableAdapterManager.UpdateAll(VideoDBDataSet)
RecTableTableAdapter.Fill(VideoDBDataSet.RecTable)
VideoDBDataSet.AcceptChanges()
Else
'No row selected to work with
End If
End Sub
'The pAddForm MUST be open for this routine to work
Private Sub UpdateGridFromForm()
Dim RowIndex As Int32
Dim Index As Int32
Dim RecIndex As Int32
Dim dt As DataTable
If ((pAddForm Is Nothing) = False) Then
RowIndex = pAddForm.GridIndex
If (RowIndex >= 0) Then
Index = pAddForm.Index
If (Index = dgvRec.Rows(RowIndex).Cells(constRecGridColIndex).Value) Then
'OK, we have a match so we are good to go
Call PopulateGridFields(RowIndex)
Else
MsgBox("Unable to save data back to the Grid because the record is no longer the same")
End If
Else
'This must be a NEW record
Call AddRecord()
End If
Else
'No form to work with
End If
End Sub
'Populate the dgvRec fields from pAddForm
Private Sub PopulateGridFields(RowIndex As Int32)
dgvRec.Rows(RowIndex).Cells(constRecGridTitle).Value = pAddForm.tTitle.Text
dgvRec.Rows(RowIndex).Cells(constRecGridCast).Value = pAddForm.tCast.Text
dgvRec.Rows(RowIndex).Cells(constRecGridAKA).Value = pAddForm.tAKA.Text
dgvRec.Rows(RowIndex).Cells(constRecGridRelated).Value = pAddForm.tRelated.Text
dgvRec.Rows(RowIndex).Cells(constRecGridGenre).Value = pAddForm.tGenre.Text
dgvRec.Rows(RowIndex).Cells(constRecGridRated).Value = pAddForm.tRated.Text
dgvRec.Rows(RowIndex).Cells(constRecGridRelease).Value = pAddForm.tRelease.Text
dgvRec.Rows(RowIndex).Cells(constRecGridLength).Value = pAddForm.tLength.Text
dgvRec.CommitEdit(RowIndex)
dgvRec.EndEdit()
Validate()
RecTableBindingSource.EndEdit()
RecTableTableAdapter.Update(VideoDBDataSet.RecTable)
RecTableAdapterManager.UpdateAll(VideoDBDataSet)
RecTableTableAdapter.Fill(VideoDBDataSet.RecTable)
VideoDBDataSet.AcceptChanges()
End Sub
'This all works great.
'The only problem I have now is that the DataGridView will
'always'Repopulate the grid (including any changes with 'Add/Delete/Modify) sending the active
'row back to the top of the grid
'I will work on a solution to this now that I have the rest working

Autofill TextBox/Checkbox from a previous TextBox' value (VB.NET Database)

Note: I'm using Visual Studio, original work was on SQL Server, moved to VB.NET
I have a Textbox "ViewStatusTxt", next to it there's a Button "ViewStatusBtn"
Below it there's a TextBox "ViewNAMETxt", another TextBox "ViewACTIVITYTxt" and then a Checkbox "ModifyStatusCB"
I'm trying to auto-fill the Checkbox AND the Textbox based on the ID input there, however I really have no clue about it since I'm new to VB.NET
Here's the code used
Private Sub IDSearch(StatusViewBtn As String)
' ADD SEARCH QUERY PARAMETERS - WITH WILDCARDS
SQL.AddParam("#StatusViewBtn", StatusViewBtn)
'RUN QUERY - SEARCH GIVES THOSE RESULTS
SQL.ExecQuery(" SELECT
aID,
Name,
Status,
Activity
FROM
[dbo].[initialTable]
WHERE
aID = #StatusViewBtn
ORDER BY
aID ASC")
End Sub
That's the function's code, which is fully working since it's a smaller version of the same one I used in a Search Page
Here's the button's function, which I'm sure is where I'm having problems, unless I need to add a specific function to the ViewNAMETxt
Private Sub StatusViewBtn_Click(sender As Object, e As EventArgs) Handles StatusViewBtn.Click
IDSearch(StatusViewBtn.Text)
ViewNAMETxt.Text = SQL.ExecQuery("SELECT
Name
FROM
initialTable
WHERE
aID = #StatusViewBtn")
End Sub
And I haven't even started on the Checkbox, viewing how the first one caused me issues. Hopefully the solution would be similar to both of them.
Thanks for reading guys, and sorry for the newbie question
1- Suppose you have a table named YourTable(int KeyColumn, string StringColumn, boolean BooleanColumn)
2- Create a form and put 2 textboxes and a checkbox and a button on it. KeyColumnTextBox, StringColumnTextBox, BooelanColumnCheckBox, SearchButton
3- In click event handler for SearchButton put the codes:
Private Sub SearchButton_Click(sender As Object, e As EventArgs) Handles SearchButton.Click
Dim connection = New SqlConnection("Your Connection string here")
Dim command = New SqlCommand("SELECT StringColumn, BooleanColumn FROM YourTable WHERE KeyColumn=#KeyColumn", connection)
command.Parameters.Add(New SqlParameter("#KeyColumn", Int32.Parse(KeyColumnTextBox.Text)))
connection.Open()
Dim reader = command.ExecuteReader()
While reader.Read()
StringColumnTextBox.Text = reader.GetString(0)
BooleanColumnCheckBox.Checked = reader.GetBoolean(1)
End While
End Sub
Don't forget to Imports System.Data.SqlClient at top of your file.

Error: "Reference to a non-shared member requires an object reference"

I am building a small app that is pulling script from within an object. I'm down to the portion where my code is pulling back the field from the object that has the script and I'm getting this error.
"reference to a non-shared member requires an object reference"
I'm not sure what to change or how to get around this. Does anyone out there have any suggestions?
Here is the code that i have so far. It's a simple app that has a combobox that you choose the company from and on button click it will get the script and show it in the textbox.
Here is my code:
Imports System.IO
Public Class Form1
Public M3System As MILLSYSTEMLib.System
Public M3Script As MILLCOMPANYLib.CScripting
Public Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'On Error Resume Next
Try
Dim Approved As Integer
' Create a Millennium system obects
M3System = CreateObject("MillSystem.System")
M3System.Load("Millennium")
'run login script
Dim User As Object = M3System.Login()
' See if login worked
If User Is Nothing Then
'MsgBox("Login failed!")
Approved = 0
Else
'MsgBox("Login successful")
'if approved=1 then the user is able to access M3
Approved = 1
End If
'populate combo box
For Each Company In M3System.Companies
cb_COID.Items.Add(Company.Field("co").ToString)
Next
Catch ex As Exception
Me.Close()
End Try
End Sub
Public Sub btn_LoadScript_Click(sender As Object, e As EventArgs) Handles btn_LoadScript.Click
Dim CoCode As String = cb_COID.SelectedItem
Dim script As String = M3Script.vbscript
If IsNothing(cb_COID) Then
MessageBox.Show("Select a Company Code")
End If
For Each CoCode In M3Script.co
tb_Script.Text = script
Next
End Sub
I'm guessing that the line you are failing on is Dim script As String = M3Script.vbscript
If that is the case, it's because you are declaring the M3Script, but you are not creating an instance of it.
Try setting the object somewhere by adding M3Script = new MILLCOMPANYLib.CScripting to your code, or however you would load it (perhaps M3Script = CreateObject("MillSystem.Script")?)

already an open DataReader associated with this Command when am chekcing with invalid data

Private Sub txt_sname_GotFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles txt_sname.GotFocus
Dim fcs As String
fcs = "select fname,dept from nstudent where stid = '" & txt_sid.Text & "'"
scmd1 = New SqlCommand(fcs, con)
dr1 = scmd1.ExecuteReader
If dr1.HasRows Then
Do While (dr1.Read)
txt_sname.Text = dr1.Item(0)
cmb_dept.Text = dr1.Item(1)
Loop
Else
MsgBox("Not Found")
End If
scmd1.Dispose()
If Not dr1.IsClosed Then dr1.Close()
End Sub
The above code for data from database and pass to textbox. When am running the program and checking with data which already present in database, it working properly. but checking with some other data(which not present in db)following error is occurring and exiting.
error:
"There is already an open DataReader associated with this Command which must be closed first."
pls help me..
Some observations:
Instead of using a global command object, use a local one. Especially since you are creating a new command anyway. And it looks like this is true of the dr1 as well.
You are not preventing SQL injection, so someone can type text in txt_sid that causes security issues by deleting data, dropping tables, or getting access to other data in the database.
You are looping and setting the same variables multiple times. If there is only going to be one record, don't bother looping.
Wrap the entire thing around a try/catch
Private Sub txt_sname_GotFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles txt_sname.GotFocus
Dim cmd1 As SqlCommand = New SqlCommand(fcs, "select fname,dept from nstudent where stid = #stid")
cmd1.Parameters.Parameters.AddWithValue("#stid", txt_sid.Text)
Dim studentReader as SqlDataReader
Try
studentReader = scmd1.ExecuteReader
If studentReader.Read Then
txt_sname.Text = studentReader.Item(0)
cmb_dept.Text = studentReader.Item(1)
Else
MsgBox("Not Found")
End If
Finally
studentReader.Close()
cmd1.Dispose()
End Try
End Sub
Finally, I think you might want to actually do this when txt_sid changes, not when txt_sname gets focus.