I am creating a windows form that the user will fill out whenever we add a new employee. If I fill in the form completely, I can write to the database, but if one field gets left blank, I get an error. In the SQL table, all rows are set to allow nulls, and I can insert via SQL Server Management Studio with null values with no problems.
For brevity, I have left off a dozen or so fields, but the same error occurs if I replace this code with the code I am using.
Dim DBConnection As New SqlClient.SqlConnection
Dim cmd As New SqlClient.SqlCommand
Try
WOWConnection.ConnectionString = "Server=MyServerName;Database=Employee Database;Trusted_Connection=TRUE;"
WOWConnection.Open()
cmd.Connection = WOWConnection
cmd.CommandText = "INSERT INTO [dbo].[Employees]([FirstName],[LastName]) VALUES (#FirstName,#LastName)"
cmd.Parameters.AddWithValue("#FirstName", Me.EMP_FirstName.Text)
cmd.Parameters.AddWithValue("#LastName", Me.EMP_LastName.Text)
cmd.ExecuteNonQuery()
MsgBox("Sucess!")
Catch ex As Exception
MsgBox("error")
Finally
DBConnection.Close()
End Try
How do you handle a field being left blank on a windows form?
Test the value being inserted before passing the value to SQL.
Note that NULL value and Empty is not the same in SQL.
cmd.CommandText = "INSERT INTO [dbo].Employees VALUES
(#FirstName,#LastName)"
If Not string.IsNullOrEmpty(Me.EMP_FirstName.Text) Then
cmd.Parameters.AddWithValue("#FirstName", Me.EMP_FirstName.Text)
Else
cmd.Parameters.AddWithValue("#FirstName", DBNull.Value)
EndIf
cmd.ExecuteNonQuery()
Related
net and would to have the Header Text of columns in a datagridview be named after results from the database, e.g the query in my code returns four dates,30/08/2017,04/09/2017,21/09/2017 and 03/02/2018. My aim is to have the column headers in the data grid named after those dates. Your help will highly be appreciated.
sql = "SELECT COUNT (ServiceDate) As NoOfServiceDates FROM (SELECT DISTINCT ServiceDate FROM tblattendance)"
Using command = New OleDbCommand(sql, connection)
Using reader = command.ExecuteReader
reader.Read()
ColumnNo = CInt(reader("NoOfServiceDates")).ToString
End Using
End Using
DataGridView1.ColumnCount = ColumnNo
For i = 0 To DataGridView1.Columns.Count - 1
sql = "SELECT DISTINCT ServiceDate FROM tblattendance"
Using command = New OleDbCommand(sql, connection)
Using reader = command.ExecuteReader
While reader.Read
DataGridView1.Columns(i).HeaderText = reader("ServiceDate").ToString
End While
End Using
End Using
Next
The current code re-runs the query each time through the column count loop, meaning it will set the column header for that column to all of the date values in sequence, so the last value in the query shows in the all the columns. You only need to run the query once:
Dim i As Integer = 0
sql = "SELECT DISTINCT ServiceDate FROM tblattendance"
Using command As New OleDbCommand(sql, connection), _
reader As OleDbDatareader = command.ExecuteReader()
While reader.Read
DataGridView1.Columns(i).HeaderText = reader("ServiceDate").ToString
i+= 1
End While
End Using
Additionally, this still results in two separate trips to the database, where you go once to get the count and again to get the values. Not only is this very bad for performance, it leaves you open to a bug where another user changes your data from one query to the next.
There are several ways you can get this down to one trip to the database: loading the results into memory via a List or DataTable, changing the SQL to include the count and the values together, or adding a new column each time through the list. Here's an example using the last option:
DataGridView1.Columns.Clear()
Dim sql As String = "SELECT DISTINCT ServiceDate FROM tblattendance"
Using connection As New OleDbConnection("string here"), _
command As New OleDbCommand(sql, connection)
connection.Open()
Using reader As OleDbDataReader = command.ExecuteReader()
While reader.Read
Dim column As String = reader("ServiceDate").ToString()
DataGridView1.Columns.Add(column, column)
End While
End Using
End Using
Even better if you can use something like Sql Server's PIVOT keyword in combination with the DataGridView's AutoGenerateColumns feature for DataBinding, where you will write ONE SQL statement that has both column info and data, and simply bind the result set to the grid.
The For Next is incorrect. You execute your command for every column, when you only need to execute it once. The last result from the DataReader will be the header for every column as currently written.
You should iterate through your DataReader and increment the cursor variable there:
Dim i As Integer = 0
Using command = New OleDbCommand(sql, connection)
Using reader = command.ExecuteReader
While reader.Read
DataGridView1.Columns(i).HeaderText = reader("ServiceDate").ToString
i += 1
End While
End Using
End Using
I have 3 columns that need populating when a user presses 'search' however every time that I click 'search' only the Employee ID appears, neither the 'First Name' nor the 'Last Name' are present in the List View. The data does exist in my Access Database, this is proved as the program produces a blank record instead of a null value error. The code that I am using to populate the List View is:
ds.Clear()
lstClockin.Items.Clear()
con.ConnectionString = provider & datafile
con.Open() 'Open connection to the database
sqlstatement = "SELECT * FROM [EmployeeAccounts]"
da = New OleDb.OleDbDataAdapter(sqlstatement, con)
da.Fill(ds, "allmembers") 'Fill the data adapter
con.Close()
Dim recordCount, x As Short
recordCount = 0
x = 0
recordCount = ds.Tables("allmembers").Rows.Count
With ds.Tables("allmembers")
Do Until x = recordCount
lstClockin.Items.Add(.Rows(x).Item(0))
lstClockin.Items(x).SubItems.Add(.Rows(x).Item(1))
lstClockin.Items(x).SubItems.Add(.Rows(x).Item(2))
lstClockin.Items(x).SubItems.Add(.Rows(x).Item(3))
x = x + 1
Loop
End With
The first 3 columns in the Database are, [Employee ID], [First Name] & [Last Name]
Any suggestions are welcome; however I have ruled out using a DataGridView or any control. As this program needs to use a ListView. Thankyou in advance!
There are several things that can be improved in the code:
Dim SQL = "SELECT Id, Name, Fish FROM Sample"
Using dbcon As New OleDbConnection(ACEConnStr)
Using cmd As New OleDbCommand(SQL, dbcon)
dbcon.Open()
Dim lvi As ListViewItem
myLV.SuspendLayout()
Using rdr = cmd.ExecuteReader
Do While rdr.Read
lvi = New ListViewItem(rdr.GetInt32(0).ToString)
If rdr.IsDBNull(1) Then
lvi.SubItems.Add("")
Else
lvi.SubItems.Add(rdr.GetString(1))
End If
If rdr.IsDBNull(2) Then
lvi.SubItems.Add("")
Else
lvi.SubItems.Add(rdr.GetString(2))
End If
myLV.Items.Add(lvi)
Loop
End Using
myLV.ResumeLayout()
End Using
End Using
Connections and other DB Provider objects allocate resources which need to be released or your app will leak. Using blocks for things that implement Dispose will close and dispose of them for you
There is no need for an DataAdapter, DataSet and DataTable since you are copying the data to the control. This code uses a DataReader to get the data.
Rather than SELECT * the query specifies the columns/order so it can use the Getxxxxx methods to get typed data. That doesnt matter a great deal in this case because everything gets converted to string for the ListView. lvi.SubItems.Add(rdr(COLUMN_NAME).ToString()) would also work.
It seems unlikely the ID column could be null, so the code only checks the other 2 for DbNull (another thing the DGV can handle without help).
Since the ListView is suboptimal and slow in adding items, SuspendLayout and ResumeLayout are used to minimize paints while it is populated.
I am not at all sure what ...the program produces a blank record means, but in order use a ListView like it is a grid, the View property must be Details and you have to have added 3 columns in the IDE (or manually create them in code). Nothing will show without those settings.
If the DataTable is needed/used elsewhere, you can still fill one without a DataAdpater and populate the LV from it:
...
dt.Load(cmd.ExecuteReader)
For Each row As DataRow In dt.Rows
lvi = New ListViewItem(row(0).ToString())
If DBNull.Value.Equals(row(1)) Then
lvi.SubItems.Add("")
Else
lvi.SubItems.Add(row(1).ToString())
End If
If DBNull.Value.Equals(row(2)) Then
lvi.SubItems.Add("")
Else
lvi.SubItems.Add(row(2).ToString())
End If
myLV.Items.Add(lvi)
Next
This uses a different DBNull check since it is using a DataRow and not the DataReader.
I'm trying to databind a textbox to a totaliser value in an access database. I currently update the database via OleDbCommand and then edit any existing entries via databinding on the form.
I have everything working fine, but I want a textbox to show the totaliser (sum) of a particular column in the datbase. Access shows this totaliser underneath the column if the database is opened.
Is there a method to bind this value to the textbox?
Thanks
Well, however you are accessing the database, you need to make a call to get the SUM of your desired table.
If we are talking about SQL, it would look something like:
conn = New OleDbConnection(Get_Constring)
conn.Open()
cmd.Connection = conn
cmd.CommandType = CommandType.Text
sSQL = " SELECT SUM(total) AS Total From YourTable"
cmd.CommandText = sSQL
OleDbDataReader dr = cmd.ExecuteReader()
If dr.Read() Then
set total = Convert.ToInt32(dr["Total"])
End If
You could load this into a DataTable/DataSet or use the DataReader and assign the result (the sum) to a textbox, like:
TextBox1.Text = total
If you are using Linq it could look like (this is just an example):
Dim yourObject = From cust In db.Customers
Group By cust.City
Into Average(cust.Orders.Count)
Order By Average
DataGridView1.DataSource = yourObject
I have several rows in DataGridView control. And i want to insert each row into database. I tried like this. But it gives error that parameter is already added. How to add parameter name once and then add values each time and execute it each time?
Using connection As New SqlCeConnection(My.Settings.databaseConnectionString)
Using command As New SqlCeCommand("INSERT INTO table_master(item, price) VALUES(#item, #price)", _
connection)
connection.Open()
For Each r As DataGridViewRow In dgvMain.Rows
If (Not String.IsNullOrWhiteSpace(r.Cells(1).Value)) Then
command.Parameters.AddWithValue("#item", r.Cells(1).Value.Trim)
command.Parameters.AddWithValue("#price", r.Cells(2).Value)
command.ExecuteNonQuery()
End If
Next
End Using
End Using
Add the parameters outside the loop and inside the loop update only their values
Using connection As New SqlCeConnection(My.Settings.databaseConnectionString)
Using command As New SqlCeCommand("INSERT INTO table_master(item, price) VALUES(#item, #price)", _
connection)
connection.Open()
' Create and add the parameters, just one time here with dummy values or'
' use the full syntax to create each single the parameter'
command.Parameters.AddWithValue("#item", "")
command.Parameters.AddWithValue("#price", 0)
For Each r As DataGridViewRow In dgvMain.Rows
If (Not String.IsNullOrWhiteSpace(r.Cells(1).Value)) Then
command.Parameters("#item").Value = r.Cells(1).Value.Trim
command.Parameters("#price").Value = r.Cells(2).Value
command.ExecuteNonQuery()
End If
Next
End Using
End Using
Using AddWithValue is a nice shortcut, but has its drawbacks. For example, it is unclear what datatype is required for the column Price. Using the Parameter constructor you could specify the exact datatype for the parameter and avoid a possible conversion mistake
Dim p = new SqlCeParameter("#price", SqlDbType.Decimal)
command.Parameters.Add(p)
......
I'm having some trouble updating changes I made to a datatable via a dataadapter. I am getting "Concurrency violation: the UpdateCommand affected 0 of 10 rows"
'Get data
Dim Docs_DistributedTable As New DataTable("Docs_Distributed")
Dim sql = "SELECT DISTINCT CompanyID, SortKey, OutputFileID, SequenceNo, DeliveredDate, IsDeliveryCodeCounted, USPS_Scanned FROM Docs_Distributed_Test"
Using sqlCmd As New SqlCommand(sql, conn)
sqlCmd.CommandType = CommandType.Text
Docs_DistributedTable.Load(sqlCmd.ExecuteReader)
End Using
'Make various updates to some records in DataTable.
'Update the Database
Dim sql As String = "UPDATE Docs_Distributed "
sql += "SET DeliveredDate = #DeliveredDate "
sql += "WHERE SequenceNo = #SequenceNo"
Using transaction As SqlTransaction = conn.BeginTransaction("ProcessConfirm")
Try
Using da As New SqlDataAdapter
da.UpdateCommand = conn.CreateCommand()
da.UpdateCommand.Transaction = transaction
da.UpdateCommand.CommandText = sql
da.UpdateCommand.Parameters.Add("#DeliveredDate", SqlDbType.DateTime).SourceColumn = "DeliveredDate"
da.UpdateCommand.Parameters.Add("#SequenceNo", SqlDbType.Int).SourceColumn = "SequenceNo"
da.ContinueUpdateOnError = False
da.Update(Docs_DistributedTable)
End Using
transaction.Commit()
Catch ex As Exception
transaction.Rollback()
End Try
End Using
Now here's the catch. I am selecting DISTINCT records and essentially getting one row per SequenceNo. There may be many rows with the same SequenceNo, and I am hoping this will update them all. I'm not sure if this is related to my problem or not.
Your select is from "Docs_Distributed_Test" and your update is to "Docs_Distributed" - this may be the cause of your issue. Are the sequence ID's the same? (If not then perhaps it is indeed affecting 0 rows with it's update).
Other than that, you can always disable optimistic concurrency on your table-adapter and it will no longer enforce the validation (Though in this case that would likely result in no error but not updating any rows).
I don't understand the Microsoft-specific aspects of this, plus VB is often hard to follow. But this sequence seems suspect:
Using transaction As SqlTransaction = conn.BeginTransaction("ProcessConfirm")
Try
Using da As New SqlDataAdapter
da.UpdateCommand = conn.CreateCommand()
da.UpdateCommand.Transaction = transaction
conn.BeginTransaction is followed by conn.CreateCommand(). Isn't that a) useless, b) hazardous to the connection state, or c) potentially a race condition?