Performance issue with SQLite database with VB.NET - vb.net

I am Inserting the data-table into SQLite Database. I am doing like this.
First I Fetch the data with getdata function and insert it into datatable, then with For Each Loop i made the Insert Command and Execute It. I am having 50000 Records it will take 30 Minutes to run.
Please Guide the suitable approach. Here is the Code.
Dim xtable As DataTable = getdata("select * from tablename")
Dim str As String = Nothing
For Each r As DataRow In xtable.Rows ''''HERE IT WILL TAKE TOO MUCH TIME
str = str & ("insert into tablename values(" & r.Item("srno") & "," & r.Item("name"));")
Next
EXECUTEcmd(str)
Public Function getdata(ByVal Query As String) As DataTable
connectionString()
Try
Dim mds As New DataTable
Dim mycommand As New SQLiteCommand(DBConn)
mycommand.CommandText = Query
Dim reader As SQLiteDataReader = mycommand.ExecuteReader()
mds.Load(reader)
Return mds
Catch ex As Exception
MsgBox("DB Error", vbCritical, "")
MsgBox(Err.Description)
Return Nothing
End Try
End Function
Public Sub EXECUTEcmd(ByVal selectcmd As String)
Using cn = New SQLiteConnection(conectionString)
cn.Open()
Using transaction = cn.BeginTransaction()
Using cmd = cn.CreateCommand()
cmd.CommandText = selectcmd
cmd.ExecuteNonQuery()
End Using
transaction.Commit()
End Using
cn.Close()
End Using
End Sub
here the Conncection String is:
conStr = "Data Source=" & dbpath & ";Version=3;Compress=True; UTF8Encoding=True; PRAGMA journal_mode=WAL; cache=shared;"

Use a stringbuilder to build your string, not string concatenation
Dim strB As StringBuilder = New StringBuilder(100 * 50000)
For Each r As DataRow In xtable.Rows
strB.AppendLine($"insert into tablename values({r.Item("srno")},{r.Item("name")});")
Next
Strings cannot be changed in .net. Every time you make a new string VB has to copy everything out of the old string into a new one and add the new bit you want. If each of your insert statements is 100 bytes, that means it copies 100 bytes, then adds 100, then copies 200 bytes and adds 100, then copies 300 bytes, then 400 bytes, then 500 bytes. By the time it has done 10 strings it has made 5.5 kilobytes of copying. By the time it's done 50 thousand strings it has copied 125 gigabytes of data. No wonder it's slow!
Always use a StringBuilder to build massive strings
--
I'm willing to overlook the sql injection hacking nag for this one, because of the nature of the task, but please read http://bobby-tables.com - you should never, ever concatenate values into an SQL as a way of making an sql that has some varying effect.
This entire exercise would be better done as this (pseudocode) kind of thing:
Dim sel as New SQLiteCommand("SELECT a, b FROM table", conn)
Dim ins as New SQLiteCommand("INSERT INTO table VALUES(:a, :b)", conn)
ins.Parameters.Add("a" ...)
ins.Parameters.Add("b" ...)
Dim r = sel.ExecuteReader()
While r.Read()
ins.Parameters("a") = r.GetString(0)
ins.Parameters("b") = r.GetString(1)
ins.ExecuteNonQuery()
End While
That is to say, you minimize your memory by reading rows one at a time out of ther edaer and inserting them one at a time in the insert; the insert command is prepared once, you just change the parameter values, execute it, change them again, execute it ... It's what parameterized queries were designed for (as well as stopping your app getting hacked when someone puts SQL in your variable, or even just stopping it crashing when you have an person named O'Grady

Maybe you must refactor your code like this:
Dim xtable As DataTable = getdata("select * from tablename")
Using cn = New SQLiteConnection(conectionString)
cn.Open()
Using transaction = cn.BeginTransaction()
Try
Using cmd = cn.CreateCommand()
cmd.Transaction = transaction
For Each r As DataRow In xtable.Rows ''''HERE IT WILL TAKE TOO MUCH TIME
cmd.CommandText = "insert into tablename values(" & r.Item("srno") & "," & r.Item("name") & ")"
cmd.ExecuteNonQuery()
Next
End Using
transaction.Commit()
Catch ex As Exception
transaction.Rollback()
End Try
End Using
End Using
Public Function getdata(ByVal Query As String) As DataTable
connectionString()
Try
Dim mds As New DataTable
Dim mycommand As New SQLiteCommand(DBConn)
mycommand.CommandText = Query
Dim reader As SQLiteDataReader = mycommand.ExecuteReader()
mds.Load(reader)
Return mds
Catch ex As Exception
MsgBox("DB Error", vbCritical, "")
MsgBox(Err.Description)
Return Nothing
End Try
End Function
Instead of concatenate an possible giant string, wrap all your inserts into a single transaction, like above. This will reduce the memory used and also make sqlite perform faster.

Related

Visual Studio Vb.net loaded dataset from oracle query missing or replacing first row of data

I have a vb.net application program that is suppose to query a oracle/labdaq database and load the dataset into a datatable. For some reason the query works fine and there is no exception thrown, however, when I load the data it seems to be missing the first row. I first noticed the issue when I did a query for a single row and it returned zero rows when I checked the datatable's row amount during debugging. I then compared all my data sets to a data miner application straight from the oracle source and i seems to always be missing one, the first, row of data when I use the application.
here is the code... I changed the query string to something else to maintain company privacy
Private Sub CaqOnSQL(strFileDirect As String)
Try
Dim connString As String = ConfigurationManager.ConnectionStrings("CA_Requisition_Attachments.Internal.ConnectionString").ConnectionString
Dim conn As New OracleConnection With {
.ConnectionString = connString
}
Dim strQuerySQL As String = "SELECT * FROM REQUISITIONS " &
"WHERE DATE BETWEEN TO_DATE('12/10/2020','MM/dd/yyyy') AND " &
"TO_DATE('12/14/2020','MM/dd/yyyy') " &
"ORDER BY ID"
conn.Open()
Dim Cmd As New OracleCommand(strQuerySQL, conn) With {
.CommandType = CommandType.Text
}
Dim dr As OracleDataReader = Cmd.ExecuteReader()
dr.read()
Dim dt As New DataTable
dt.TableName = "RESULTS"
dt.Load(dr)
ExcelFileCreation(dt, strFileDirect)
You should remove the line:
dr.read()
The call to Read is what is causing you to skip the first row, when combined with the DataTable Load method.
In addition, I have taken the liberty to make some additional changes to your code for the purposes of Good Practice. When using Database objects like Connection and Command, you should wrap them in Using blocks to ensure the resources are released as soon as possible.
Dim dt As New DataTable()
dt.TableName = "RESULTS"
Using conn As New OracleConnection(connString)
conn.Open()
Dim strQuerySQL As String = "SELECT * FROM REQUISITIONS " &
"WHERE DATE BETWEEN TO_DATE('12/10/2020','MM/dd/yyyy') AND " &
"TO_DATE('12/14/2020','MM/dd/yyyy') " &
"ORDER BY ID"
Using command = New OracleCommand(strQuerySQL , conn)
Using dataReader = command.ExecuteReader()
dt.Load(dataReader)
End Using
End Using
End Using
Note: Be wary of Using blocks when using a DataReaderas you may find the connection is closed when you don't want it to be. In this case, the DataReader is used entirely within this function and is safe to use.

Vb.NET MS Access SQL UPDATE Syntax Issue

I have been hammering away at this for hours but haven't gotten it right so I decided to post.
My Vb.NET app has a 2 Jpgs on the harddrive. One is LowRes and the other is HighRes. I want to save both pictures in the same record in a Microsoft Access 2007 database via INSERT, which works fine, and then UPDATE to add the second one. The UPDATE is not working.
I pieced together a bunch of code I found online for the INSERT code (learning as I go) and eventually figured something out that actually worked. However, trying to tweak the function to do a UPDATE to add the second picture is proving difficult. I suspect its something to do with me specifying parameters wrong?
The database has 1 table Records with 3 columns RecordID (which is Text, and a autonumber primary key which is pulled from Form1.Tb_RecordID.Text), HighRes (which is OLE) and LowRes (Which is also OLE). The database is named Database.accdb.
I call the subroutine with:
Save_To_Database("LowProfile.jpg", "LowRes")
if I want it to INSERT an OLE image in the "LowRes" column. I then call
Update_To_Database("HighProfile.jpg", "HighRes")
to update the record with the HighRes picture. I eventually want to consolidate these functions one to one and use ByVals to determine if it should Update or Insert.
This works fine:
Sub Save_To_Database(ByVal Filename As String, ByVal Res As String)
Dim cnString As String = "PROVIDER=Microsoft.ACE.OLEDB.12.0;Data Source=Database.accdb"
Dim theQuery As String = "INSERT INTO Records([RecordID],[" & Res & "]) values (" & Form1.Tb_RecordID.Text & ", #Img)"
Try
Dim fs As FileStream
fs = New FileStream(Filename, FileMode.Open, FileAccess.Read)
Dim picByte As Byte() = New Byte(fs.Length - 1) {}
fs.Read(picByte, 0, System.Convert.ToInt32(fs.Length))
fs.Close()
Dim CN As New OleDbConnection(cnString)
CN.Open()
Dim imgParam As New OleDbParameter()
imgParam.OleDbType = OleDbType.Binary
imgParam.ParameterName = "Img"
imgParam.Value = picByte
Dim cmd As New OleDbCommand(theQuery, CN)
cmd.Parameters.Add(imgParam)
cmd.ExecuteNonQuery()
MessageBox.Show("Image successfully saved.")
cmd.Dispose()
CN.Close()
CN.Dispose()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
However, my UPDATE code fails with a syntax error on the SQL statement:
Sub Update_To_Database(ByVal Filename As String, ByVal Res As String)
Dim cnString As String = "PROVIDER=Microsoft.ACE.OLEDB.12.0;Data Source=Database.accdb"
Dim theQuery As String = "UPDATE Records SET ([" & Res & "]) values (#Img) WHERE RecordID =" & Form1.Tb_RecordID.Text
MsgBox(theQuery)
Try
Dim fs As FileStream
fs = New FileStream(Filename, FileMode.Open, FileAccess.Read)
Dim picByte As Byte() = New Byte(fs.Length - 1) {}
fs.Read(picByte, 0, System.Convert.ToInt32(fs.Length))
fs.Close()
Dim CN As New OleDbConnection(cnString)
CN.Open()
Dim imgParam As New OleDbParameter()
imgParam.OleDbType = OleDbType.Binary
imgParam.ParameterName = "Img"
imgParam.Value = picByte
Dim cmd As New OleDbCommand(theQuery, CN)
cmd.Parameters.Add(imgParam)
cmd.ExecuteNonQuery()
MessageBox.Show("Image successfully saved.")
cmd.Dispose()
CN.Close()
CN.Dispose()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Anyone see the problem? Or a way to improve the code?
Your original problem is the fact that you tried to write an update statement using the syntax of an insert statement. An easy fix would be simply to change the update statement to the correct syntax.
However, there are a number of things in your code that could be and should be changed, so I wrote an example based on your code that still can be improved further:
Sub Save_To_Database(ByVal Filename As String, ByVal RecordId As String, ByVal IsHighResolution As Boolean, ByVal IsNew As Boolean)
Dim theQuery As String
Dim cnString As String = "PROVIDER=Microsoft.ACE.OLEDB.12.0;Data Source=Database.accdb"
If IsNew Then
theQuery = "INSERT INTO Records([RecordID],[" & IIf(IsHighResolution, "HighRes", "LowRes") & "]) values (#RecordId, #Img)"
Else
theQuery = "UPDATE Records SET " & IIf(IsHighResolution, "HighRes", "LowRes") & " = #Img WHERE RecordID = #RecordId"
End If
Try
Using fs As New FileStream(Filename, FileMode.Open, FileAccess.Read)
Dim picByte As Byte() = New Byte(fs.Length - 1) {}
fs.Read(picByte, 0, System.Convert.ToInt32(fs.Length))
Using CN As New OleDbConnection(ConnectionString)
Dim cmd As New OleDbCommand(theQuery, CN)
cmd.Parameters.Add("Img", OleDbType.Binary).Value = picByte
cmd.Parameters.Add("#RecordId", OleDbType.Integer).Value = RecordId
CN.Open()
cmd.ExecuteNonQuery()
MessageBox.Show("Image successfully saved.")
End Using
End Using
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Points of interest:
Insert and update is now handled by the same method, only difference is a Boolean parameter that specifies if this is a new record or an existing one.
Queries are now safe from sql injection. The only concatenation here is the column name that you can't parameterize in sql, but it is decided inside the sub itself. When you use the sub, you only pass a boolean value indicating of this is a high resolution image.
The Using block is recommended to use whenever you are using anything that implements the IDisposable interface. This ensures the correct closer and disposal even in a case of an exception being thrown.
Parameters code simplified.

ms sql stored procedure return data without output

Hey all i am trying to find examples of getting data back from a stored procedure that has no parameters sent to it nor has any returned output parameter. Though it does display data.
How can i get that from my code im using below?
Dim myCommandSQL As New SqlCommand
Dim myReaderSQL As SqlDataReader = Nothing
Dim intX As Integer = 0
Dim connSql As SqlConnection
Try
connSql = New SqlConnection("Server=sqlprod;" & _
"Database=ISS3_PROD;" & _
"User ID=xxx;" & _
"Password=xxx;" & _
"Trusted_Connection=False;")
connSql.Open()
myCommandSQL.CommandType = CommandType.StoredProcedure
myCommandSQL.CommandText = "Select_Prod"
Dim sqlParReturn1 As System.Data.SqlClient.SqlParameter = myCommandSQL.Parameters.Add("#return_value", SqlDbType.VarChar)
sqlParReturn1.Direction = ParameterDirection.Output
myCommandSQL.ExecuteNonQuery()
MsgBox(sqlParReturn1)
connSql.Close()
myCommandSQL.Dispose()
The #return_value i just put there to see what would happen but i got nothing returned.
Any help would be great!
David
If you assign a parameter to your command, then your stored procedure should take a parameter. Furthermore, if you specify the direction as Output, then you should mark that parameter as OUTPUT in your stored procedure.
If you just want the results of a stored procedure that doesn't take any parameters, then remove all the lines that include sqlParReturn1. Also, your command isn't a "non-query" -- you are querying for data. To get it, you should do this (I also refactored your code using some better-practice techniques):
Using connSql As SqlConnection = New SqlConnection(...)
connSql.Open()
Using myCommandSQL As SqlCommand = connSql.CreateCommand()
myCommandSQL.CommandType = CommandType.StoredProcedure
myCommandSQL.CommandText = "Select_Prod"
Using reader As SqlDataReader = myCommandSQL.ExecuteReader()
If reader.HasRows Then
While reader.Read()
// loops through the rows returned
End While
End If
End Using
End Using
End Using
Here is the MSDN documentation on reading data using an ADO datareader. I think their example explains this quite well, so I have just copy and pasted the example here. Just replace your SQL setup, and then you just need to call the ExecuteReader, followed by a while loop that runs while reader.Read finds rows. Just inside the loop, you can then access your columns via reader.Get..., using ordinals or columnnames.
Private Sub HasRows(ByVal connection As SqlConnection)
Using connection
Dim command As SqlCommand = New SqlCommand( _
"SELECT CategoryID, CategoryName FROM Categories;", _
connection)
connection.Open()
Dim reader As SqlDataReader = command.ExecuteReader()
If reader.HasRows Then
Do While reader.Read()
Console.WriteLine(reader.GetInt32(0) _
& vbTab & reader.GetString(1))
Loop
Else
Console.WriteLine("No rows found.")
End If
reader.Close()
End Using
End Sub

How do I assign the results of an SQL query to multiple variables in VB.NET?

This is my first attempt at writing a program that accesses a database from scratch, rather than simply modifying my company's existing programs. It's also my first time using VB.Net 2010, as our other programs are written in VB6 and VB.NET 2003. We're using SQL Server 2000 but should be upgrading to 2008 soon, if that's relevant.
I can successfully connect to the database and pull data via query and assign, for instance, the results to a combobox, such as here:
Private Sub PopulateCustomers()
Dim conn As New SqlConnection()
Dim SQLQuery As New SqlCommand
Dim daCustomers As New SqlDataAdapter
Dim dsCustomers As New DataSet
conn = GetConnect()
Try
SQLQuery = conn.CreateCommand
SQLQuery.CommandText = "SELECT Customer_Name, Customer_ID FROM Customer_Information ORDER BY Customer_Name"
daCustomers.SelectCommand = SQLQuery
daCustomers.Fill(dsCustomers, "Customer_Information")
With cboCustomer
.DataSource = dsCustomers.Tables("Customer_Information")
.DisplayMember = "Customer_Name"
.ValueMember = "Customer_ID"
.SelectedIndex = -1
End With
Catch ex As Exception
MsgBox("Error: " & ex.Source & ": " & ex.Message, MsgBoxStyle.OkOnly, "Connection Error !!")
End Try
conn.Close()
End Sub
I also have no problem executing a query that pulls a single field and assigns it to a variable using ExecuteScalar. What I haven't managed to figure out how to do (and can't seem to hit upon the right combination of search terms to find it elsewhere) is how to execute a query that will return a single row and then set various fields within that row to individual variables.
In case it's relevant, here is the GetConnect function referenced in the above code:
Public Function GetConnect()
conn = New SqlConnection("Data Source=<SERVERNAME>;Initial Catalog=<DBNAME>;User Id=" & Username & ";Password=" & Password & ";")
Return conn
End Function
How do I execute a query so as to assign each field of the returned row to individual variables?
You probably want to take a look at the SqlDataReader:
Using con As SqlConnection = GetConnect()
con.Open()
Using cmd As New SqlCommand("Stored Procedure Name", con)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.Add("#param", SqlDbType.Int)
cmd.Parameters("#param").Value = id
' Use result to build up collection
Using dr As SqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection Or CommandBehavior.SingleResult Or CommandBehavior.SingleRow)
If (dr.Read()) Then
' dr then has indexed columns for each column returned for the row
End If
End Using
End Using
End Using
Like #Roland Shaw, I'd go down the datareader route but an other way.
would be to loop through
dsCustomers.Tables("Customer_Information").Rows
Don't forget to check to see if there are any rows in there.
Google VB.Net and DataRow for more info.

Better way to print out rows from a datatable in vb.net

I am new to vb.net and I am trying to query a database and print out the records in the row to the console window. I got it to work, but I have a feeling that there is a more concise way to do this. One thing that I am sure is wrong is that I had to convert the dataset to a datatable to be able to retrieve the values. Is that correct? Could you take a look at the code below (especially the for loop) and let me know what I can improve upon?
Thanks!
Module Module1
Sub Main()
Dim constring As String = "Data Source=C:\Users\test\Desktop\MyDatabase1.sdf"
Dim conn As New SqlCeConnection(constring)
Dim cmd As New SqlCeCommand("SELECT * FROM ACCOUNT")
Dim adapter As New SqlCeDataAdapter
Dim ds As New DataSet()
Try
conn.Open()
cmd.Connection = conn
adapter.SelectCommand = cmd
adapter.Fill(ds, "testds")
cmd.Dispose()
adapter.Dispose()
conn.Close()
Dim dt As DataTable = ds.Tables.Item("testds")
Dim row As DataRow
Dim count As Integer = dt.Columns.Count()
For Each row In dt.Rows
Dim i As Integer = 0
While i <= count - 1
Console.Write(row(i))
i += 1
End While
Console.WriteLine(Environment.NewLine())
Next
Catch ex As Exception
Console.WriteLine("There was an error")
Console.WriteLine(ex)
End Try
Console.ReadLine()
End Sub
End Module
Here is how I would rewrite this for a few reasons:
1) You should always use Using statements with disposable objects to ensure they are correctly cleaned up. You had a good start with the dispose commands, but this way is safer.
2) It is more efficient to use ExecuteReader than loading everything into a dataset.
3) Your try/catch statement should include object creation as well as execution.
Finally, in response to your question about datasets and datatables, that code was absolutely correct: a dataset consists of zero or more datatables, so you were just extracting the existing datatable from the dataset.
Try
Dim constring As String = "Data Source=C:\Users\test\Desktop\MyDatabase1.sdf"
Using conn As New SqlCeConnection(constring)
conn.Open()
Using cmd As New SqlCeCommand("SELECT * FROM ACCOUNT", conn)
Dim reader As SqlCeDataReader
reader = cmd.ExecuteReader()
Do While reader.Read
For i As Integer = 0 To reader.FieldCount - 1
Console.Write(reader.GetString(i))
Next
Console.WriteLine(Environment.NewLine())
Loop
End Using
End Using
Catch ex As Exception
Console.WriteLine("There was an error")
Console.WriteLine(ex)
End Try
Console.ReadLine()
End Sub
One last note: since you are just printing to the console, it doesn't matter as much, but whenever you deal with a lot of strings, especially those that are to be concatenated, you should always consider using System.Text.StringBuilder.
Here is an example rewrite of the loop that prints to the console using stringbuilder (builds the string in memory, then dumps it to the console; I have also added the field name for good measure):
Dim sbOutput As New System.Text.StringBuilder(500)
For i As Integer = 0 To reader.FieldCount - 1
If sbOutput.Length <> 0 Then
sbOutput.Append("; ")
End If
sbOutput.Append(reader.GetName(i)).Append("=").Append(reader.GetString(i))
Next
sbOutput.AppendLine()
Console.Write(sbOutput.ToString)