I'm wondering if this is a good way of doing data access, in terms of all the database objects being properly closed and disposed? For example:
Using conn As New SqlConnection(MyConnectionString)
Using cmd As New SqlCommand("some SQL here", conn)
... add parameters ...
conn.Open()
Using dr As SqlDataReader = cmd.ExecuteReader()
While dr.Read()
... do stuff ...
Wend
End Using
End Using
End Using
Is nesting Using like acceptable practice? If I exit the method at some point within the Read() loop, will the use of Using like this ensure all objects are cleaned up properly regardless?
Using guarantees orderly disposal in an implicit try/finally block.
' THE FOLLOWING TRY CONSTRUCTION IS EQUIVALENT TO THE USING BLOCK
Dim resource As New resourceType
Try
' Insert code to work with resource.
Catch ex As Exception
' Insert code to process exception.
Finally
' Insert code to do additional processing before disposing of resource.
resource.Dispose()
End Try
Nested usings work in a similar fashion. If you exit a block of code, it will execute the finally block, and properly dispose your objects.
http://msdn.microsoft.com/en-US/library/htd05whh(v=VS.80).aspx
To add, the Using block will "behind the scenes" add a Try Finally statement. In the finally statement it will call IDisposable.Dispose on the object. In other words, no matter what you do or what happens, the object will get disposed.
Yes, this is ok. The Dispose method of IDisposable objects is always called.
PS: In this case, the Dispose method also contains the Close method.
You are writing VB.Net, so this only partially applies, but for the C# folks out there using StyleCop, multiple using statements like this will cause exception 2202 in StyleCop.
There is a lengthy section at the msdn link of dissenting comments as to the usefulness of this rule in stylecop.
I will not make a judgment call as to whether you should heed StyleCops warnings for C# in your VB.Net code.
Related
I have an application which does quite a few database queries. In order to speed up the application I do some queries which are independent of each other (it doesn't matter which order they get updated in as they aren't referencing one another) on a separate thread.
However, it appears that if two queries are executed at the same time on the different threads, but one finishes earlier, the DataAdapter attempts to close the connection which is still being used on another thread resulting in a RaceOnRCWCleanup warning:
An attempt has been made to free an RCW that is in use. The RCW is in use on the active thread or another thread. Attempting to free an in-use RCW can cause corruption or data loss.
I may be incorrect about this assumption but if I don't call any queries on the secondary thread, or delay them, I don't seem to get this issue. If I force the queries to happen at the same time, I get the warning.
My query functions are, with a few exceptions, all structured as such:
Dim strSQL As String = "SELECT..."
Try
da = New OleDb.OleDbDataAdapter(strSQL, conn)
da.Fill(dsData, "Progress")
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
Return dsData.Tables("Progress")
And da is declared in a separate module as:
Public da As OleDbDataAdapter
I thought by declaring da = New I force it to be a new object and therefore, closing the connection on one should not effect the other?
Where am I going wrong here?
I believe I have solved this by using a separate OleDbConnection object for each thread (I never call the same functions across different threads).
This way a single connection object is only ever used in serial.
I don't know if this is the correct way to handle this but it seems to work.
In my Winforms app which uses a remote database, I have the function below. (I also have two other functions which work similarly: One for scalar queries which returns zero upon failure, the other for updates and inserts which returns false upon failure.)
Currently, ALL data manipulation is pumped through these three functions.
It works fine, but overall please advise if I'd be better off establishing the connection upon launching my app, then closing it as the app is killed? Or at another time? (Again, it's a windows forms app, so it has the potential to be sitting stagnant while a user takes a long lunch break.)
So far, I'm not seeing any ill effects as everything seems to happen "in the blink of an eye"... but am I getting data slower, or are there any other potential hazards, such as memory leaks? Please notice I am closing the connection no matter how the function terminates.
Public Function GetData(ByVal Query As String) As DataTable
Dim Con As New SqlConnection(GlobalConnectionString)
Dim da As New SqlDataAdapter
Dim dt As New DataTable
Try
Con.Open()
da = New SqlDataAdapter(Query, Con)
Con.Close()
da.Fill(dt)
Catch ex As Exception
Debug.Print(Query)
MsgBox("UNABLE TO RETRIEVE DATA" & vbCrLf & vbCrLf & ex.Message, MsgBoxStyle.Critical, "Unable to retrieve data.")
End Try
da.Dispose()
Con.Close()
Return dt
End Function
There are exceptions to this, but best practices in .Net do indeed call for creating a brand new connection object for most queries. Really.
To understand why is, first understand actually connecting to a database involves lots of work in terms of protocol negotiations, authentication, and more. It's not cheap. To help with this, ADO.Net provides a built-in connection pooling feature. Most platforms take advantage of this to help keep connections efficient. The actual SqlConnection, MySqlConnection, or similar object used in your code is comparatively light weight. When you try to re-use that object, you're optimizing for the small thing (the wrapper) at the expense of the much larger thing (the actual underlying connection resources).
Aside from the benefits created from connection pooling, using a new connection object makes it easier for your app to scale to multiple threads. Imagine writing an app which tries to rely on a single global connection object. Later you build a process which wants to spawn separate threads to work on a long-running task in the background, only to find your connection is blocked, or is itself blocking other normal access to the database. Worse, imagine trying to do this for a web app, and getting it wrong such that the single connection is shared for your entire Application Domain (all users to the site). This is a real thing I've seen happen.
So this is something that your existing code does right.
However, there are two serious problems with the existing method.
The first is that the author seems not to understand when to open and when to close a connection. Using the .Fill() method complicates this, because this method will open and close your connection all on its own.1 When using this method, there is no good reason to see a single call to .Open(), .Close(), or .Dispose() anywhere in that method. When not using the .Fill() method, connections should always be closed as part of a Finally block: and the easiest way to do that is with Using blocks.
The second is SQL Injection. The method as written allows no way to include parameter data in the query. It only allows a completed SQL command string. This practically forces you to write code that will be horribly vulnerable to SQL Injection attacks. If you don't already know what SQL Injection attacks are, stop whatever else you're doing and go spend some time Googling that phrase.
Let me suggest an alternative method for you to address these problems:
Public Function GetData(ByVal Query As String, ParamArray parameters() As SqlParameter) As DataTable
Dim result As New DataTable()
Using Con As New SqlConnection(GlobalConnectionString), _
Cmd As New SqlCommand(Query, Con),
da As New SqlDataAdpapter(Cmd)
If parameters IsNot Nothing Then Cmd.Parameters.AddRange(parameters)
Try
da.Fill(result)
Catch ex As Exception
Debug.Print(Query)
'Better to allow higher-level method to handle presenting the error to the user
'Just log it here and Rethrow so presentation tier can catch
Throw
End Try
End Using 'guarantees connection is not left hanging open
Return result
End Function
1See the first paragraph of the "Remarks" section.
This isn't a real "answer" to my own question, but I have something to add and I wanted to add some code.
To Joe: Thank you, my code is well on the way to using parameterized queries almost exclusively. Although I knew what SQL injection attacks were, and that they're a pretty big deal, here's my exuse: In the past I had used stored procedures for parameterized queries, and I kinda hate writing those and for the first year my code will be used only within by my small company of 5 employees who are family members... I had planned to switch everything to stored procedures later if I sold the software. This approach is better and I will probably not need stored procedures at all.
I especially like how elegantly parameterized queries handle dates, as I don't have to convert dates to appropriate text. Much easier.
Anopther advantage I'm seeing: Sometimes a "Save button" must execute either Insert or Update, depending on whether the record displayed is new. Using parameters allows me to write two alternate short basic queries, but to use the same parameters for either with less code.
Overall, this means a whole lot less code-intensive construction of the query string.
The part I didn't have, and I learned to do it elsewhere, was assigning the parameter array, calling the procedure, so I'm including an example here hoping others find it useful:
Dim query As String = "Select Phone from Employees where EmpNo = #EmployeeNumber and Age = #Age"
Dim params As SqlParameter() = {
New SqlParameter("#EmployeeNumber", txtEmployeeNumber.Value),
New SqlParameter("#Age", txtAge.Value)
}
Dim Phone as String = GetData(query, params).Rows.Item(0)
I have an object that I created that handles SQL queries. The function that actually handles the query is:
Function Execute_Command(ByVal strSQLCommand As String)
Dim sqlReturnInfo As SqlDataReader
Dim sqlQuery As New SqlCommand
sqlQuery = sqlMainDBCon.CreateCommand()
sqlQuery.CommandText = strSQLCommand
sqlReturnInfo = sqlQuery.ExecuteReader
Return sqlReturnInfo
End Function
When I execute the code it works fine the first time, but the 2nd time I get an error saying that sqlReturnInfo is not closed.
I fixed the problem by putting sqlReturnInfo = Nothing at the top after the declaration.
My understanding though, is that once this function finishes, the sqlReturnInfo should go out of scope and be destroyed automatically, and then once the function runs again the sqlReturnInfo is recreated as a new object, therefore sqlReturnInfo = nothing should not be nescessary/
I have seen this type of issue in one or two other areas in my code as well, out of scope variables persisting...
Am I doing something wrong, or is my understanding of variable scope incorrect?
Thanks
-RW
Garbage Collection - not all objects follow that rule(COM objects, Streams, graphics objects, Fonts, Bitmaps(the worst), etc...), dispose when done no matter what method you use(Sub/Function). Using/End Using blocks are the best - it takes care of that for you. My rule of thumb, if you see a Dispose method on a class object - Use It! The reason they have them is the engineer knew it would have problems getting collected so they made a method to assist with it.
Your object has the following statement on it:
You must explicitly call the Close method when you are through using the SqlDataReader to use the associated SqlConnection for any other purpose.
You need to use the .Close() function when you are done:
sqlReturnInfo.Close()
See: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.close%28v=vs.110%29.aspx
Take code like this:
Dim dt As New DataTable("Table")
Dim sda As New SqlDataAdapter(pQuery, pCon)
sda.SelectCommand.CommandTimeout = pCommandTimeout
sda.Fill(dt)
sda.Dispose()
Return dt
and:
Dim myconnection As SqlConnection = New SqlConnection(DBValues.m_sDBString)
myconnection.Open()
Try
Dim com As New SqlCommand(cmd, myconnection)
com.ExecuteNonQuery()
com.Dispose()
Catch ex As Exception
Throw ex
Finally
If myconnection.State = ConnectionState.Open Then
myconnection.Close()
myconnection.Dispose()
End If
End Try
When is it getting excessive to explicity call things like Close() and Dispose()? Because apparently the GC handles this internally most of the time, but not all the time (?). So far I haven't really seen somebody put down a very clear and unambiguous explanation on how to distinguish between when this is excessive and when it isn't, or what the difference is between doing this explicitly and just letting the GC handle it. Could somebody explain this? Thanks!
If you aren't doing anything more complicated than what's in the example, then it is probably excessive.
You could use the using keyword to make things simpler. It automates the calling of the Dispose method of objects from classes that implement IDisposable.
Relevant documentation on the using keyword, for VB.NET
Also note that the Dispose method of SqlConnection calls its Close method too (running the same state validation you do in your code).
Notice that there are situations where you might have to call Dispose manually instead of going with using. For example, you might want to use a data reader in more than one context, so using using in either of them may not be desired. You'll know when you're in one of these situations, and then calling Dispose yourself won't be overkill. But only then.
Using sr As New System.IO.StreamWriter("C:\test.txt")
sr.WriteEnd
End Using
Using sr As New System.IO.StreamReader("C:\test.txt")
sr.ReadToEnd
End Using
This is equivalent to:
sr = New System.IO.StreamWriter("C:\test.txt")
sr.WriteEnd
sr.Dispose
sr = New System.IO.StreamReader("C:\test.txt")
sr.ReadToEnd
sr.Dispose
Am I right in saying the attempt to read the file may fail if the Garbage collector had not got around to disposing the StreamWriter and closing the file. If so where is the benefit is using the Using Statement in this case. Is is better practice to use
Using sr As New System.IO.StreamWriter("C:\test.txt")
sr.WriteEnd
sr.close()
End Using
It is a best practice to always the Using statement because if an exception is thrown during the reading the stream might never be disposed and your application will leak a file handle. If this is a desktop application the process will probably die and release the handle but in a web application not releasing unmanaged resources such as file handles could be catastrophic.
Also you don't need to call Close on your stream if you are calling Dispose.
Remark: if you want to read the contents of a text file into a string you could directly do this instead of going through the pain of instantiating a StreamReader, reading it to the end and disposing it:
Dim data = File.ReadAllText("C:\test.txt");
No, the Garbage Collector does not dispose the object, calling Dispose does that, and the using block makes sure that happens. The object may still be in memory, but the file will have been dealt with.
If you didn't call close or dispose (either manually or by use of the using block), and the object got collected it would then be finalised (slowing up garbage collection) and that would close the file handle. But this last-chance is different to dispose.
You don't have to call close on the file within the using block, but you are free to. This may be a good idea to release the handle quickly (not often an issue with file handles, more often an issue with pooled database connections), with the using block giving you an added guarantee (it's always safe to call dispose, even if its already been called).
Actually, it is equivalent to this: The Finally blocks will guarantee the Dispose is called and everything is cleaned up in the event of an exception.
Try
sr = New System.IO.StreamWriter("C:\test.txt")
sr.WriteEnd()
Finally
sr.Dispose()
End Try
Try
sr = New System.IO.StreamReader("C:\test.txt")
sr.ReadToEnd()
Finally
sr.Dispose()
End Try
Think if you are done and don't care too much about it after, can let the dispose just happen
BUT dispose may take a while, so if you need to use the file (e.g. pass to another app, move to new location, or any other use of the file), then probably should Close it explicitly
Close should release any locks, expect this will fail:
dim fn as string = "C:\temp.txt"
Using sr As New System.IO.StreamWriter(fn)
sr.WriteLine("Junk")
' sr.close
End Using
system.io.file.delete(fn)
Expect the delete to throw an error, since file dispose may not yet have happened
If you explicitly close (e.g. un-comment the close), then should always succeed