Clone a datarow into same table but primary key creatin error - vb.net

I'd like to allow the user to clone a record. Data is from MS SQL db and is contained in a single table with a primary key (int32) autoincrementing titled "CriteriaID". I get a CriteraID can't be null error which I would expect since it's an ID column. I tried setting it to allowdbnull in on the dataset side but no luck. How do I set it to the value needed to eventually save it into DB? If I hit new record that column is -1,-2,-3, etc. but in this case I want to clone all columns EXCEPT for the CriteriaID. I also tried NOTHING, VBNULL, and a random integer not already in DB. WOuld it be easier to use a different method?
Try
If Not IsNothing(TCriteriaBindingSource.DataSource) Then
Dim sr As DataRow() = DsCriteria.Tables("tCriteria").Select("CriteriaID = " & DsCriteria.Tables("tCriteria").Rows(TCriteriaBindingNavigator.BindingSource.Position).Item("CriteriaID"))
sr(0).Item("CriteriaID") = DBNull.Value
DsCriteria.Tables("tCriteria").NewRow()
DsCriteria.Tables("tCriteria").Rows.Add(sr(0))
End If
Catch ex As Exception
MsgBox(ex.Message)
End Try

Related

Getting "Database is Locked" when trying to move a list of records from one table to another table in SQLite

I have a Public Sub to move a collection of records from one table to another in the same SQLite database. First it reads a record from strFromTable, then writes it to strToTable, then deletes the record from strFromTable. To speed things up, I've loaded the entire collection of records into a transaction. When the list involves moving a lot of image blobs, the db gets backed up, and throws the exception "The Database is Locked". I think what is happening is that it's not finished writing one record before it starts trying to write the next record. Since SQLite only allows one write at a time, it thows the "Locked" exception.
Here is the code that triggers the error when moving a lot of image blobs:
Using SQLconnect = New SQLiteConnection(strDbConnectionString)
SQLconnect.Open()
Using tr = SQLconnect.BeginTransaction()
Using SQLcommand = SQLconnect.CreateCommand
For Each itm As ListViewItem In lvcollection
SQLcommand.CommandText = $"INSERT INTO {strToTable} SELECT * FROM {strFromTable} WHERE id = {itm.Tag}; DELETE FROM {strFromTable} WHERE ID = {itm.Tag};"
SQLcommand.ExecuteNonQuery()
Next
End Using
tr.Commit()
End Using
End Using
When I get rid of the transaction, it executes without error:
Using SQLconnect = New SQLiteConnection(strDbConnectionString)
SQLconnect.Open()
Using SQLcommand = SQLconnect.CreateCommand
For Each itm As ListViewItem In lvcollection
SQLcommand.CommandText = $"INSERT INTO {strToTable} SELECT * FROM {strFromTable} WHERE id = {itm.Tag}; DELETE FROM {strFromTable} WHERE ID = {itm.Tag};"
SQLcommand.ExecuteNonQuery()
Next
End Using
End Using
I'm not very good with DB operations, so I'm sure there is something that needs improvement. Is there a way to make SQLite completely finish the previous INSERT before executing the next INSERT? How can I change my code to allow using a transaction?
Thank you for your help.
.
Ok ... here is the solution that I decided to go with. I hope this helps someone finding this in a search:
Dim arrIds(lvcollection.Count - 1) As String
Dim i as Integer = 0
' Load the array with all the Tags in the listViewCollection
For i = 0 to lvcollection.Count - 1
arrIds(i) = lvcollection(i).Tag 'item.Tag holds the Primary Key "id" field in the DB
Next
'build a comma-space separated string of all ids from the array of ids.
Dim strIds as String = String.Join(", ", arrIds)
Using SQLconnect = New SQLiteConnection(strDbConnectionString)
SQLconnect.Open()
Using tr = SQLconnect.BeginTransaction()
Using SQLcommand = SQLconnect.CreateCommand
SQLcommand.CommandText = $"INSERT INTO {strToTable} SELECT * FROM {strFromTable} WHERE id IN ({strIds});"
SQLcommand.ExecuteNonQuery()
SQLcommand.CommandText = $"DELETE FROM {strFromTable} WHERE ID IN ({strIds});"
SQLcommand.ExecuteNonQuery()
End Using
tr.Commit()
End Using
End Using
The IN statement allows me to pass all of the "id" values to be deleted as a batch. This solution is faster and more secure than doing them one by one with no transaction.
Thanks for the comments, and best wishes to everyone in their coding.

How to keep incrementing the same value?

Try
Dim count as Int64
Using cm As New SQLiteCommand("SELECT COUNT([RollNo]) FROM [StudentTbl]", cn)
If Not cm.ExecuteScalar() Is DBNull.Value Then
count = Convert.ToInt64(cm.ExecuteScalar())
Else
count = 0
End If
End Using
Catch ex As Exception
MsgBox(ex.Message)
Return
End Try
txtRollNo.Text = count +1
Move the count declaration outside the Try block otherwise it will not be visible outside the block. You attempt to use it outside the block.
You cannot assign a number to a .Text property. It requires a String. Convert count + 1 to a String.
Keep your database objects local so you can control that they are closed and disposed. This is particulary important for connections which are precious resources.
Why are you executing your query twice?
In most SQL languages Count will not return null. It will return 0 if there are no rows the match criteria.
It pains me to think that you have a Class level variable that is an Open connection. Get rid of it if you do. In my code you must open the connection before executing the command.
Good job converting the return value of the ExecuteScalar. Also good job passing the command text and the connection to the constructor of the command.
I am a bit leery of why you want this number. If you are expecting to use count +1 for the next primary key -- DON'T. If this is a multi-user environment then it will not work. Even if it is single user, suppose you have deleted a few records. Your method will give you a duplicate Primary Key. Set your RollNo field to auto-increment/identity and the database will do it for you.
Private Sub GetCount()
Dim count As Int64
Try
Using cn As New SQLiteConnection("Your connection string")
Using cm As SQLiteCommand = New SQLiteCommand("SELECT COUNT([RollNo]) FROM [StudentTbl]", cn)
cn.Open()
count = Convert.ToInt64(cm.ExecuteScalar())
End Using
End Using
Catch ex As Exception
MsgBox(ex.Message)
Return
End Try
txtRollNo.Text = CStr(count + 1)
End Sub

how to clear the previous search from the textbox?

I am new in vb.net. i have a database and i am running a search query of employee records when search button clicked and display that information in textbox, however when a user is not in the database, the search output is displaying the information of the previous search. the information in textbox should display blank or say "No record found" if the user is not in the record. not sure what is wrong in my code.
Try
myConnection.Open()
Dim str As String
str = "SELECT * FROM tblEmp WHERE (EmpID = '" & ADS.UserEmpID & "')"
Dim cmd As OleDbCommand = New OleDbCommand(str, myConnection)
dr = cmd.ExecuteReader
While dr.Read
If dr.HasRows > 0 Then
MessageBox.Show("user already in the system", "Warning", MessageBoxButtons.OK)
ElseIf dr.HasRows = 0 Then
MessageBox.Show("Not Onboarded", "Warning", MessageBoxButtons.OK)
End If
BGC1 = dr("PreStartChecks").ToString
BGC2 = dr("EmpName").ToString
myConnection.Close()
End While
Catch ex As Exception
MsgBox("Unable to Connect to BGC DB. You may not have access or DB not available." &
ex.ToString)
End Try
Your While loop and If statement don't make sense. Firstly, HasRows is type Boolean so testing whether it is greater than zero is nonsensical. Secondly, Read returns False if there are no rows so the only way you can get to that If statement is if there is at least one row to read so testing HasRows when you already know that there are rows is also nonsensical. The proper option here is to use just an If statement and test Read only:
If dr.Read() Then
'There is a row to read and it was just read, so you can now get the data from the reader.
Else
'There is no row to read.
End If
If you want to clear a control when there's no data, you do so in the Else block.
The "rules" about when and how to use HasRows and Read are very simple and logical:
If all you care about is whether the query result set contains data or not but you don't care what that data is, just use an If statement to test HasRows. The HasRows property is type Boolean so there's no need to compare it to anything. It already is True or False.
If there can only be zero or one row in the result set, just use an If statement to call Read and test the result. Again, it's type Boolean so there's no need to compare it to anything. If it returns True then you can access the data for the row you just read.
If there can be multiple rows and you don't want to do anything special if there are no rows then just use a While or Do While loop to call Read and access the row that was just read inside the loop.
If there can be multiple rows and you do want to do something special if there are no rows, use an If statement to test HasRows and then a While or Do While loop inside the If block to call Read. You would handle the case where there are no rows in the Else block.
Assuming that txtBGC1 and txtBGC2 are TextBoxes, you could do something like this, assuming that the query can at most return one employee
...
If dr.Read Then ' There is an employee
txtBGC1.Text = dr("PreStartChecks").ToString
txtBGC2.Text = dr("EmpName").ToString
Else ' There is no employee
txtBGC1.Text = ""
txtBGC2.Text = "No record found"
End If
myConnection.Close()

How to write to DB using Linq

I have a Database with a Table named Fruits. In the table I have 3 columns: Name, Date, Remove.
Now i have inserted 3 rows, with a value like
Apple----20/10/12----N
Mango----21/12/12----Y
Banana--- 15/07/12----N
Grapes----18/12/12----Y
I want to delete the 2 rows which is marked as 'Y.
This I want to achive using Linq in c# code. Initally I am establishing a connection to the DB using a connection string. I will get the device context DC, and with this I will copy all the rows.
Now I will enumerate the rows marked as N and I will call remove.
Once I do remov, I will do: dc.deleteonSubmit<tableName>(var).
After this, I should call dc.submittsavechages(); or what to make DB look like:
Mango----21/12/12----Y
Grapes----18/12/12----Y
If I query in sql query analyzer i should get above result. How to do this in LINQ?
Try this:
Dim dc as new DataClassesDataContext()
Dim deleteFruits = _
From f In db.Fruits() _
Where f.Remove _
Select f
For Each fruit In deleteFruits
db.Fruits.DeleteOnSubmit(fruit)
Next
Try
db.SubmitChanges()
Catch ex As Exception
Console.WriteLine(ex)
' Provide for exceptions
End Try
It can be done more compact
db.Fruits.DeleteAllOnSubmit(db.Fruites.Where(f=>f.remove == "Y" ))
The try/db.SubmitChanges() part remains unchanged.

SQLDataAdapter filling datatable with primary key produces error and exits sub

Ok so this is going to take some explaining.
The process I am trying to do is grab data from a table function in SQL and then fill a dataset with the returned values.
I then have to run this query twice more to query an alternative number table. Then add to the same table as the previous queries.
This needs to be as fast as possible, so I am currently using an adapter.fill to populate the datasets and then a dataset.merge to put them all into one table.
The problem is the query can return duplicates which waste time and space, because of this I made column 3(part_ID) the primary key to stop duplicates.
When this is run with the .merge it quits at the first instance of a duplication and doesn't continue with the population.
The code below is what I used to fix this, I was just wondering if there is a better more elegant solution.
com = New SqlCommand(sqlPN, myConnect)
adapter.SelectCommand = com
adapter.Fill(temp, "Table(0)")
Dim data As New DataSet
data = temp
temp.Tables(0).Columns(3).Unique = True
firstSet = temp.Tables(0).Rows.Count
temp.AcceptChanges()
If temp.Tables(0).Rows.Count < maxRecords Then
Dim sqlAlt As String = "select Top " & (maxRecords + 10 - temp.Tables(0).Rows.Count) & " * from getAltEnquiry('" & tbSearchFor.Text & "') ORDER BY spn_partnumber"
adapter.SelectCommand.CommandText = sqlAlt
adapter.FillLoadOption = LoadOption.OverwriteChanges
adapter.Fill(temp, "Table(1)")
For i = 0 To temp.Tables(1).Rows.Count - 1
Try
temp.Tables(0).ImportRow(temp.Tables(1).Rows(i))
Catch e As Exception
End Try
Next
End If
If temp.Tables(0).Rows.Count < maxRecords Then
Dim sqlSuPN As String = "select Top " & (maxRecords + 5 - temp.Tables(0).Rows.Count) & " * from getSuPNEnquiry('" & tbSearchFor.Text & "') ORDER BY spn_partnumber"
adapter.SelectCommand.CommandText = sqlSuPN
adapter.Fill(temp, "Table(2)")
For i = 0 To temp.Tables(2).Rows.Count - 1
Try
temp.Tables(0).ImportRow(temp.Tables(2).Rows(i))
Catch e As Exception
End Try
Next
End If</code>
Thanks for any help or advice ^__^
Since you are looping through the records from the additional queries and using the ImportRow, your code will throw an exception if more than one record with the same value in the primary key field is attempted to be inserted. That is the purpose of a primary key when using in this way. If you want to ensure that your table only has unique records, you will need to ensure that the records are distinct before inserting them by checking the new row's part_id value against those already in the table. However, your design isn't necessarily the ideal approach.
Since you mentioned that this needs to be fast, it will probably be best if you could write a stored procedure to return just the rows you need from all tables and do the Fill into the table once.
If that's not possible, you can call adapter.Fill on the same DataTable for each of your data sources. Use the Fill overload that takes just the DataTable to fill and as per the docs, it will merge the data together if more than one record with the same primary key exists. The way you have the Fill method called, it is creating a new DataTable with the name you provide for each time you call Fill. Instead, you want to Fill just one DataTable.
"You can use the Fill method multiple times on the same DataTable. If a primary key exists, incoming rows are merged with matching rows that already exist. If no primary key exists, incoming rows are appended to the DataTable."