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.
Related
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
I have this code in Visual Basic, every time I have a new insert:
Private _conn As SqlConnection
Public Function Include(ByVal pSql As String, Optional timeout As Integer = 120) As Boolean
Try
Dim SQL_Str = "my string of conection... with database. not put on this example"
_conn = New SqlConnection(SQL_Str)
_conn.Open()
_adapter = New SqlDataAdapter
cmd.CommandTimeout = timeout
cmd.Connection = _conn
cmd.CommandText = pSql
cmd.CommandType = CommandType.Text
_adapter.InsertCommand = cmd
_adapter.InsertCommand.ExecuteNonQuery()
Catch ex As Exception
InputBox("New Error on Sql cmd: ", , pSql)
End Try
_conn.Close()
_conn.Dispose()
_conn = Nothing
_adapter.Dispose()
_adapter = Nothing
End Function
Ok this is a straightforward way to update the database.
But supose I have 1000 connections at the same time, do the application would support this kind of approach?
Do this method support simultaneous threads acessing the _conn object?
Is it really necessary to create a pool of connections to handle data?
Do a pool of connections will really improve something?
E.g. with this I'm overloading the application instead of the database?
If so, how would I do it on VbNet/Visual Basic?
Yes, pooled connections really are faster. They keep you from needing to continually re-negotiate login and protocol information. Even better, this is already built into the SqlConnection type, and it's done in a reasonably thread-safe way (where the existing code is not).
The short version is you really do want to create a brand new connection object for most queries, and you do not want to try to share the same connection variable throughout an application or session.
Given that, I see several big problems in that code:
Treating a class-level _conn variable as if it were local, making it impossible to share instances of this class safely across threads.
No mechanism to clean up the connection if an exception is thrown (needs a Finally or Using block. Just closing after the Catch isn't good enough.
No way to pass separate query parameters in the function signature. This will force you to write horribly insecure code elsewhere that's crazy-vulnerable to sql injection attacks. It's the kind of thing where you wake up one morning to find out you were hacked over a year ago, and IMO borders on professional malpractice.
Mixing UI code with utility code.
You want something more like this:
Private cnString As String = "my string of conection... with database. not put on this example"
Public Sub Include(pSql As String, parameters() As SqlParamter, Optional timeout As Integer = 120)
Using conn As New SqlConnectioN(cnString), _
cmd As New SqlCommand(pSql, conn)
If parameters IsNot Nothing AndAlso parameters.Length > 0 Then
cmd.Parameters.AddRange(parameters)
End If
conn.Open()
cmd.ExecuteNonQuery()
End Using
End Sub
And you might call it like this (assuming type or instance name is DB):
Dim pSql As String = "INSERT INTO [ExampleTable] (FirstName, LastName, CreationDate) VALUES (#FirstName, #LastName, #CreationDate)"
Dim parameters = {
New SqlParameter("#FirstName", SqlDbType.NVarChar, 20),
New SqlParameter("#LastName", SqlDbType.NVarChar, 20),
New SqlParameter("#CreationDate", SqlDbType.DateTime)
}
parameters(0).Value = "John"
parameters(1).Value = "Smith"
parameters(2).Value = DateTime.Now
Try
DB.Include(pSql, parameters)
Catch ex As Exception
MessageBox.Show(String.Format("New Error on Sql cmd:{0}{1}{0}{0}Message:{2}",vbCrLf, pSql, ex.Message)
End Try
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
cn.Open()
Dim arrimage() As Byte
Dim ms As New MemoryStream()
If (pb1.Image IsNot Nothing) Then
pb1.Image.Save(ms, pb1.Image.RawFormat)
arrimage = ms.GetBuffer
ms.Close()
End If
With cmd
.Connection = cn
.CommandText = "INSERT INTO [Example]([PName],[Pic])VALUES(#a2,#a1)"
.Parameters.Add("a0", OleDbType.VarChar).Value = tName.Text
.Parameters.Add("a1", OleDbType.Binary).Value = IIf(pb1.Image IsNot Nothing, arrimage, DBNull.Value())
.Dispose()
.ExecuteNonQuery()
End With
cn.Close()
End Sub
You have multiple issues in your code. In order of appearance:
Dont use GetBuffer()
As noted on MSDN, the buffer can be up to twice the size of the data in the stream. This will bloat the database needlessly with extra nulls. Use ToArray() instead.
Since images in a DB have to be converted to and from a byte array, consider archiving the images to a folder and store just the name in the database. You can then prepend the archive folder name to load an image quickly.
Rather than RawFormat, I would encode it to something like JPEG.
Use Using blocks
Anything which has a .Dispose method usually needs to be disposed. This would apply to the OleDBCommand object (MemStream really doesnt need to be disposed, but that is an implementaion detail).
Using blocks incoporate Dim, New and Dispose in one handy, easy to use block:
Using foo As New FooBar()
...
End Using
The first line declares a foo variable, and creates an instance of FooBar which you can use inside the block. At the end, it is disposed of automatically.
Don't Use Global DBCommand objects
Your code does not show cmd being declared or created, so it must be a form level object. Don't Do That. There is nothing reusable about a DBCommand object unless all your app does is one thing.
In the code you add 2 parameters. The next time you go to use it, it could still have those 2 and the code will add 2 more which is more than the SQL query requires. In this case, the code disposes of it, but that means the next time you go to reference it you will get an ObjectDisposedException.
As noted, your code calls Dispose before ExecuteNonQuery which will crash - you cant use a disposed object.
DBNull.Value is not a method
As for the compiler error, you have this:
IIf(pb1.Image IsNot Nothing, arrimage, DBNull.Value())
DBNull.Value is a property, not a method, so the parens are not needed. Also, you should use the "new" If operator rather than the old IIF function. The operator is short-circuited so the part/clause that does not apply is ignored:
' should evaluate the data (arrimage) not its source
If(pb1.Image IsNot Nothing, arrimage, DBNull.Value) ' no parens
Revamped code:
cn.Open()
Dim arrimage() As Byte = Nothing
If (pb.Image IsNot Nothing) Then
Using ms As New MemoryStream()
pb.Image.Save(ms, ImageFormat.Jpeg)
arrimage = ms.ToArray()
End Using
End If
Dim sql = "INSERT INTO [Example]([PName],[Pic]) VALUES (#a2,#a1)"
Using cmd As New OleDbCommand(sql, cn)
cmd.Parameters.Add("a0", OleDbType.VarChar).Value = tName.Text
If arrimage IsNot Nothing Then
cmd.Parameters.Add("a1", OleDbType.VarBinary).Value = arrimage
Else
cmd.Parameters.Add("a1", OleDbType.VarBinary).Value = DBNull.Value
End If
cmd.ExecuteNonQuery()
End Using
cn.Close()
Since the command object is useless without both the query and the connection, I prefer to pass them in the constructor. It makes the code shorter as well as assures that it has what it needs
Connections also ought to be created, used and disposed of each time
It is also easy to create a handy extension method for converting an image to a byte array
Will the following statement cause a memory leak:
Imports System.Data.SQLClient
Public Function getConnection () As SQLConnection
return New SQLConnection()
End Function
Public Sub TestConnection()
Dim con As SQLConnection
con = getConnection
con.close
con = Nothing
End Sub
How does .close or .dispose get called on the SQLConnection in getConnection?
You're returning a reference type, hence you operate on the same instance in TestConnection so no memory leak here.
At the end you have 2 instances with null (gc will collect them), but the connection is closed.
There will be no memory leak because it will be garbage collected after you've called the method.
But this method does nothing but causing confusion. You should always dispose connections(which closes it implicitely) as soon as you're finished with it (even in case of an exception).
You can do that in a finally of a Try/Finally or (easier) with the Using statement. But since both approaches need to wrap the connection, your method enables the calling method to forget it. Therefore it is bad practise.
So simply do this:
Public Sub TestConnection()
Using con = New SqlConnection("connection string here")
Using cmd = new SqlCommand("sql query here", con)
' do something, f.e. cmd.ExecuteNonQuery() '
End Using
End Using
End Sub
I have an ASP.Net 2.0 Web Forms Application using SQL Server 2008. The application has a UI layer and Data Access Layer. I use Enterprise Libray 5.0 to persist the data.
Recently my site has been running very slow, especially on pages where there are maybe 15-20 separate reads from the database. I am very concerned that my SqlDataReader database connections are not being closed properly. Below is an example of how code works. Please take a look and let me know if you see any issues with it in terms of leaking connections.
Data Access Class
Public Class DataAccess
Private db As Database = DatabaseFactory.CreateDatabase()
Public Function ExecuteDataReader(ByVal params() As SqlParameter, ByVal SProc As String) As SqlDataReader
Dim i As Integer
Dim dr As SqlDataReader = Nothing
Dim cmd As DbCommand
cmd = db.GetStoredProcCommand(SProc)
cmd.CommandTimeout = 120
For i = 0 To params.Length - 1
db.AddInParameter(cmd, params(i).ParameterName.ToString, params(i).DbType, params(i).Value)
Next
dr = TryCast(DirectCast(db.ExecuteReader(cmd), RefCountingDataReader).InnerReader, SqlDataReader)
Return dr
End Function
UI Code Behind Page
Dim drSource As SqlDataReader = Nothing
Try
Dim params(0) As SqlParameter
params(0) = New SqlParameter("#applicant_id", Session("ApplicantID"))
drSource = DataAccess.ExecuteDataReader(params, "sp_get_date_last_login")
If drSource.HasRows Then
drSource.Read()
'Do code
End If
Finally
If Not (drSource Is Nothing) Then
drSource.Close()
End If
End Try
I tried to put the code below into my ExecuteDataReader method, but this then closes the SqlDataReader before it gets a chance to do a Read
if (cmd.Connection.State == ConnectionState.Open)
cmd.Connection.Close();
Can someone please look at the code above and let me know how to properly close my database connections, or maybe I am already doing it?
Thanks for your help folks.
Have you tried getting the underlying call to ExecuteReader to take the CommandBehavior.CloseConnection parameter? At least this will ensure that the connection is closed when the DataReader is also closed. This will rely on consumers of the DataReader passed back from ExecuteDataReader() to close it explicitly or dispose via a Using block.
Alternatively, try adding the following to the code behind after the drSource.Close() line:
drSource.Connection.Close()