How do I deal with "An attempt has been made to free an RCW that is in use. " Error - vb.net

I know this question might be duplicate questions but I have a problem and need a solution to overcome it.
I've made a project and used functions and Sub everywhere.
One of the function/Sub is like,
Public Sub ExecuteQuery(Xcn As OleDbConnection)
Try
If Xcn.State = ConnectionState.Open Then Xcn.Close()
Xcn.Open()
cmd.Connection = Xcn
cmd.ExecuteNonQuery()
Xcn.Close()
Catch e As Exception
Throw e
End Try
End Sub
I just use executequery(con) instead of writing whole sentence everytime.
Now the question is that I created a bw_worker and running a sub that includes small subs like I showed above asynchronously.
For example, A sub is that I run async like,
Private Sub RunCode()
dim cmd as new oledbcommand("Select * from table",con)
if con.state = ConnectionState.closed then con.open()
execute reader stuff here
if con.state = ConnectionState.Open then con.close()
ExecuteQuery(con)
cmd = new Oledbcommand("Select * from Table2",con)
ExecuteQuery(con)
End Sub
I don't know if its good practice or not but now problem arises.
I am trying to create a loading screen for some time taking functions and subs so I referred this Link and faced the error.
NOTE: I understood the error it gives but I want to know a workaround if possible. Like something possible without changing a lot of code.

If you keep your database objects local they won't be open on another thread. Commands too. This code will demonstrate how to use Using blocks which will close and dispose of your database objects even if there is an error. Please no .AddWithValue. The .Add method forces you to name a datatype which will help with visually providing a clue that the datatype passed in matches. There are also several database reasons to use .Add. See https://www.dbdelta.com/addwithvalue-is-evil/ and https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/
It is okay to have a class level variable for the connection string so you don't have to type it all the time.
Private ConnString As String = "Your connection string"
Private Sub RunCode()
Using con As New OleDbConnection(ConnString)
Using cmd As New OleDbCommand("Select * from table", con)
con.Open()
Using reader = cmd.ExecuteReader
'reader stuff here
End Using
End Using
End Using
End Sub

Related

Disposable class keeps reference

I'm using simple DataReader commands very often in my project.
To simplify it, I've created a function:
Public Function DataReaderFromCommand(ByRef uCn As SQLite.SQLiteConnection, ByVal uCommandText As String) As SQLite.SQLiteDataReader
Dim nCmdSel As SQLite.SQLiteCommand = uCn.CreateCommand
With nCmdSel
.CommandText = uCommandText
End With
Dim r As SQLite.SQLiteDataReader = nCmdSel.ExecuteReader
Return r
End Function
In my project I use it like this:
Using r As SQLite.SQLiteDataReader = DataReaderFromCommand(cnUser, "SELECT * FROM settings")
Do While r.Read
'do something
Loop
End Using'this should close the DataReader
In one case, I need to delete my database. However this fails with the error "File is locked by another process".
I tried to isolate the problem, and the locking occurs because of the function "DataReaderFromCommand".
Does anybody see what I'm doing wrong / what keeps the database locked?
I thought that after "End Using" of the datareader, the SQLiteCommand would also be disposed, so there are no further reference to the database.
You should probably be trying to do it this way:
Public Sub UsingDataReader(ByVal connectionString As String, ByVal commandText As String, ByVal action As Action(Of SQLite.SQLiteDataReader))
Using connection As New SQLite.SQLiteConnection(connectionString)
Using command As New SQLite.SQLiteCommand(commandText, connection)
Using reader = command.ExecuteReader()
action(reader)
End Using
End Using
End Using
End Sub
Then you can call the code like this:
UsingDataReader("/* your connection string here */", "SELECT * FROM settings", _
Sub (r)
Do While r.Read
'do something
Loop
End Sub)
This ensures that all of the disposable references are closed when the Sub has completed.
The first problem is that not all the disposables are being disposed of. We are assured that the connection passed to that helper is in a Using block, but the command also needs to be disposed of as it has a reference to the connection:
Dim cmd As New SQLiteCommand(sql, dbcon)
Even if you dont use the overloaded constructor, in order to work, somewhere you set the connection property. This illustrates one of the problems with such "DB helper" methods: the DBConnection, DBCommand and DBReader objects work together very closely, but they are created in different methods with different scopes and you can't normally see if everything is being cleaned up properly.
The code posted will always fail because that DBCommand object - and by extension the DBConnection - are not disposed. But even if you clean up properly, pooling will keep the DBConnection alive for a while as jmcilhinney explains. Here are 2 fixes:
Clear the Pool
Using dbcon As New SQLiteConnection(LiteConnStr),
cmd As New SQLiteCommand(sql, dbcon)
dbcon.Open()
Dim n As Int32 = 0
Using rdr = cmd.ExecuteReader
While rdr.Read
' == DoSomething()
Console.WriteLine("{0} == {1}", n, rdr.GetString(0))
n += 1
End While
End Using
' Clears the connection pool associated with the connection.
' Any other active connections using the same database file will be
' discarded instead of returned to the pool when they are closed.
SQLiteConnection.ClearPool(dbcon)
End Using
File.Delete(sqlFile)
The dbCon and cmd objects are "stacked" into one Using statement to reduce indentation.
This will close and discard any and all connections in the pool, provided they have been Disposed - as well as any objects which reference them. If you use Dim cmd ... you will need to explicitly dispose of it.
Force Garbage Collection
I think this is much more ham-fisted, but it is included for completeness.
Using dbcon As New SQLiteConnection(LiteConnStr),
cmd As New SQLiteCommand(sql, dbcon)
...
Using rdr = cmd.ExecuteReader
...
End Using
End Using
GC.WaitForPendingFinalizers()
File.Delete(sqlFile)
This also works as long as everything has been properly disposed of. I prefer not to mess with GC unless absolutely necessary. The issue here is that clean up will not be limited to DBProvider objects but anything which has been disposed and is awaiting GC.
Yet a third workaround would be to turn off pooling, but you would still have to dispose of everything.
You are going to need to also close your cnUser connection to the database.
Closing/disposing the reader does not necessarily close/dispose the open connection.

How to close database connection?

I'm having a problem in project of mine in VB.NET. The problem is whenever I want to save, delete or update data with an Access database I get an error with a message saying that "not allowed to change the connection string property. connection's current state is open".
I have used If con.State = ConnectionState.Open Then con.Close() End If
command using finally in every section where I have called the database.
But still I'm having the same problem. What am I doing wrong?
Use the "USING"-Keyword. Exiting a using block calls .Dispose() on the object which for a SqlConnection will close the connection and any open resources.
Using connection As New SqlConnection(connection)
Dim command As New SqlCommand("Select * From dbo.table1",connection)
command.ExecuteNonQuery()
End Using
EDIT:
Module Module1
Public Sub DbConnection()
Dim connectionString as String = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=yourServerAddress;Initial Catalog=university.mdb;
Integrated Security=SSPI;"
Using connection as New Sqlconnection(connectionstring)
Dim command As New SqlCommand("Select * From dbo.table1",connection)
command.ExecuteNonQuery()
End Using
End Sub
End Module

How can I use multiple connection in If else code blocks?

I want to use multiple connection in if else code block but it's giving the following error when I checked in a Messagebox :
"Argument prompt cannot be converted to string"
Here is my code :
Try
Conn.Open()
Com.CommandText = "Select * FROM Table1 WHERE ID=" & txtID.Text & " AND DOR=#01/01/1900# AND Paid = '0' ORDER BY DOI"
Dr = Com.ExecuteReader
If Dr.Read = True Then
txtInstNo.Text = Dr(2)
txtInstAmount.Text = Dr(4)
Else
If MsgBox("Wait! You're not allowed to do it. Do you still want to continue ? ", MsgBoxStyle.YesNo Or MsgBoxStyle.Question, "Alert") = MsgBoxResult.Yes Then
Try
Dim Con As New OleDbConnection
Dim Comm As New OleDbCommand
Dim Dr2 As OleDbDataReader
Con.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Database.accdb"
Con.Open()
Comm.Connection = Con
Comm.CommandText = "Select * FROM Table1 WHERE ID=" & txtID.Text & " AND DOR=#01/01/1900# AND Paid = '0' ORDER BY DOI"
Comm.CommandType = CommandType.Text
Dr2 = Comm.ExecuteReader
MsgBox(Dr2) <-- Here I got that error
If Dr.Read = True Then
txtInstNo.Text = Dr(2)
txtInstAmount.Text = Dr(4)
Else
MsgBox("Sorry, no record found",MsgBoxStyle.Exclamation, "Alert")
End If
Dr2.Close()
Con.Close()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End If
End If
Dr.Close()
Conn.Close()
Catch ex As Exception
MsgBox(ex.Message)
End Try
Your query might be returning entire rows, objects, widgets or whatever. As #Andrew Morton pointed out, it's a Data Reader. It's not going to implicitly convert your result. You'll have to manipulate your reader result and convert it to a string to do anything useful. You'll also have to handle if your DataReader returns a null result, which when converted should be "" an empty string.
If you just want to see what Dr2 contains, you could try MsgBox(CStr(Dr2)). No error handling, if it throws an exception.
There are a few things which could be modified in your code to make it a bit shorter and so easier to track down what is not working. It is easier to isolate a problem if you have the minimal amount of code which shows the problem: How to create a Minimal, Complete, and Verifiable example.
I'll show my suggestion for a minimal amount of code on a new form which should help you narrow down where the problem is, and then I'll go over why I have written it that way and what could be going wrong.
Option Strict On
Option Infer On
Imports System.Data.OleDb
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim connStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Database.accdb"
Dim sql = "SELECT InstNo, InstAmount FROM Table1 WHERE ID = ? AND DOR = #01/01/1900# AND PAID='0' ORDER BY DOI"
' lists to hold the retrieved data
Dim instNos As New List(Of String)
Dim instAmounts As New List(Of String)
' get the data
Using conn As New OleDbConnection(connStr)
Using cmd As New OleDbCommand(sql, conn)
cmd.Parameters.Add(New OleDbParameter With {.ParameterName = "#ID",
.DbType = DbType.String,
.Value = txtID.Text})
conn.Open()
Dim rdr = cmd.ExecuteReader()
While rdr.Read
instNos.Add(rdr.GetString(0))
instAmounts.Add(rdr.GetString(1))
End While
End Using
End Using
' act on the data
If instNos.Count = 0 Then
MsgBox(String.Format("No records were found for ID ""{0}"".", txtID.Text))
Else
txtInstNo.Text = String.Join(vbCrLf, instNos)
txtInstAmount.Text = String.Join(vbCrLf, instAmounts)
End If
End Sub
End Class
The code.
I start with Option Strict On to make sure that all data types match up and I haven't done anything else silly which Visual Studio can point out to me.
I use Option Infer On so that I don't have to type out the type of variables when the compiler can infer what they are.
I used just one Button on the form along with the three named textboxes as we are going for minimal code.
I set up the two strings which are going to be used in one place at the top of the sub because it is easier to maintain the code that way. Normally, you would declare variables just before they are used to minimise their scope.
I specified exactly which columns I want from the database. There is no point retrieving all of them with *. I had to guess what the columns are called - you will need to put in the actual names if they are different.
The result of a query to the database might have more than one record, so I initialise Lists for the data. (Your query has an ORDER BY so I assume that there could be more than one record.)
The Using statement makes sure that resources are released cleanly whatever else happens.
For OleDb, parameters are normally represented by a ?. (If there is more than one, they are all represented by question marks and the parameters must be added in the order in which they are to be put into the query.) I had to guess at the data type for the ID column - please put in the correct type. When you create the parameter, you can still use a meaningful name for it, even though it is ignored by the computer.
Next, the data (if any) is read. I do nothing else at this point except read the data to keep it fast and tidy. I assumed that the data to be retrieved is strings, hence the GetStrings. You should adjust that if required, and also the types of the Lists to match.
Now that the data has been read, I act on it. If there was no data, show an appropriate message, and if there was data then I put it into multiline textboxes to show it. Note that I wrote multiline: if it was a single line textbox then only the last line would be visible. Other ways of displaying it could be more useful, for example a DataGridView - in which case I might have read the data into a DataTable or a list of some class.
What could go wrong.
In your query, you have AND DOR = #01/01/1900# - is this correct?
In your query, you have AND PAID = '0' - is PAID actually a string? If it is a number then it should be AND PAID = 0. (The DB should convert the string "0" to the number zero, but why make it do extra work?)
Now that you have multiline textboxes for the results, you can see if it just happens that the last records found happened to be blank, and are simply not visible in a single-line textbox.
Finally, are you sure it is using the correct database file?

vb.net polling Sql server once a second causing timeout

I was asked to do a system that would poll one table in the database every second and if it counters a row that meets a criteria start actions to handle that.
I've done this but every now and then I get a time out exception. I have a WPF application where I have a thread that runs in background. This thread has a loop and sleeps for one second at the end of the loop. The connection to the database is opened inside "using" clause.
Below is my thread sub:
Private Sub PollDatabase()
While m_StopThread = False
Try
Dim listOfRows As List(Of DataObject) = db.GetDataObjects()
... Do something with the rows ...
Catch ex As Exception
m_log.WriteLine(ex.ToString())
End Try
Thread.Sleep(1000)
End While
End Sub
And my SQL function looks like this:
Public Function GetDataObjects() As List(Of DataObject)
Dim result As New List(Of DataObject)
Dim sb As New StringBuilder("... the sql query ...")
Using cnn = New SqlConnection(_connectionString)
cnn.Open()
Using cmd = New SqlCommand(sb.ToString(), cnn)
cmd.CommandTimeout = 0
Using DataReader As SqlDataReader = cmd.ExecuteReader()
Do While DataReader.Read()
... read the columns from table
to the dataobject ...
result.Add(DataObject)
Loop
End Using
End Using
End Using
Return result
End Function
Now what seems randomly my log has a time out exception:
System.Data.SqlClient.SqlException (0x80131904): Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
...
at System.Data.SqlClient.SqlConnection.Open()
My questions are: is this at all save way of doing this? Or am I doing something fundamentally wrong here? And of course if anyone have a suggestion to fix this issue.
EDIT:
I tried a bit different approach with the SQL function. I'm now opening a connection once when my application starts and dumped the "using" clauses. So my function looks something like this now:
Public Function GetDataObjects() As List(Of DataObject)
Dim result As New List(Of DataObject)
Dim sb As New StringBuilder("... the sql query ...")
_sqlCmd.CommandText = sb.ToString()
Using DataReader As SqlDataReader = _sqlCmd.ExecuteReader()
Do While DataReader.Read()
... fill the list with objects ...
Loop
End Using
Return result
End Function
My log is clean form errors. So is there something wrong opening a connection to the server once in a second as I do with the using?
EDIT:
I've done a lot of testing now to identify the problem. What I discovered is that just connecting multiple times to the server doesn't cause any problems. Neither does adding a select statement after the connection. But when I actually implement a function where is the complete reader part and return my results I run into the time out problems. Here is two examples.
This isn't causing issues:
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs)
Me.DataContext = Me
m_Thread = New Thread(AddressOf ConnectionTestFunction)
m_Thread.IsBackground = True
m_Thread.Start()
End Sub
Private Sub ConnectionTestFunction()
While m_stopThread = False
Try
m_log.WriteLine("GetData (" & m_ThreadCounter & ")")
Using cnn As SqlConnection = New SqlConnection("Data Source=server;Initial Catalog=db;Integrated Security=True;MultipleActiveResultSets=True")
cnn.Open()
Using cmd As SqlCommand = New SqlCommand("SELECT * FROM Data", cnn)
Using DataReader As SqlDataReader = cmd.ExecuteReader()
Do While DataReader.Read()
Loop
End Using
End Using
End Using
Catch ex As Exception
m_log.WriteLine(ex.ToString())
End Try
m_ThreadCounter += 1
Thread.Sleep(1000)
End While
End Sub
This is causing timeout errors:
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs)
Me.DataContext = Me
m_Thread = New Thread(AddressOf ConnectionTestFunction)
m_Thread.IsBackground = True
m_Thread.Start()
End Sub
Private Sub ConnectionTestFunction()
While m_stopThread = False
Try
m_log.WriteLine("GetData (" & m_ThreadCounter & ")")
Dim datarows As List(Of Data) = Me.GetData()
Catch ex As Exception
m_log.WriteLine(ex.ToString())
End Try
m_ThreadCounter += 1
Thread.Sleep(1000)
End While
End Sub
Private Function GetData() As List(Of Data)
Dim result As New List(Of Data)
Using cnn As SqlConnection = New SqlConnection("Data Source=server;Initial Catalog=db;Integrated Security=True;MultipleActiveResultSets=True")
cnn.Open()
Using cmd As SqlCommand = New SqlCommand("SELECT * FROM Data", cnn)
Using DataReader As SqlDataReader = cmd.ExecuteReader()
Do While DataReader.Read()
Dim d As New Data()
d.DataId = DataReader("DataId")
... etc fields about 10 of them ...
result.Add(d)
Loop
End Using
End Using
End Using
Return result
End Function
I'm really happy if anyone have any thoughts about this... I have to admit I'm really confused now.
Probably, your code is taking longer to complete than the default time-out value for the connection is. Try specifying the time-out when you create your Sql Connection. Make sure that it's longer than the time your code needs to complete.
This approach doesn't seem very good.. Instead of pooling, why not react when new data comes? You can use a trigger or SqlDependency?
http://dotnet.dzone.com/articles/c-sqldependency-monitoring

Why do I got error when I create 2 commands on SQL in vb.net

Should I use using?
Private Sub btntest_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btntest.Click
If sqlConnection.State = ConnectionState.Closed Then
sqlConnection.Open()
End If
Dim query = "Select * from tablebusiness"
Dim cmd = New MySqlCommand(query, sqlConnection)
Dim data = cmd.ExecuteReader()
Do While data.Read
Loop
Dim cmd1 = New MySqlCommand(query, sqlConnection)
Dim data1 = cmd1.ExecuteReader //Error. Already have data reader
//Error There is already an open DataReader associated with this Connection which must be closed first.
Dim check = 1
'sqlConnection.Close()
End Sub
Although you have not let us know what the error is (which makes solving any problem much harder), I expect the issue is arising because you are trying to re-use the SqlConnection object for 2 different commands. Especially since you are not disposing your first command before initialising the second.
Firstly, use 2 different SqlConnection objects to manage the connection to the database. You are not putting any more overhead on the database or the code if you do this. Let the .NET framework connection pooling do its job - don't try to do it yourself. You don't need to do anything specific to enable connection pooling (although you can disable it by setting Pooling=false in your connection string).
Secondly use the using statement to correctly dispose your SqlConnection, SqlCommand, and SqlDataReader objects. e.g.
Using connection As New SqlConnection(connectionString)
connection.Open()
Using Command As New SqlCommand(query, connection)
Using reader As SqlDataReader = Command.ExecuteReader()
While reader.Read()
'Do Stuff'
End While
End Using
End Using
connection.Close()
End Using
You have missed the parenthesis after cmd1.ExecuteReader. It should be cmd1.ExecuteReader().
You Need another Conncetion if you want both the datareaders to work simultaneously, else close/ dispose the previous command before using cmd1.ExecuteReader()