1

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
PaulOTron2000
  • 1,115
  • 1
  • 13
  • 20
  • 2
    Do you know that there is a connection pool in .NET (actually in windows, for many many years) and connection will be kept open for some longer time to be reused? – TomTom Apr 28 '14 at 17:41
  • I was not aware of that, but how would you suggest this fact might influence my code? I believe upon exiting the function, the SqlConnection is automatically collected as garbage in .net, is it not? If so, it seems upon calling the function a second (or 100th) time, it's creating a truly new connnection, even though it still has the same name, right? – PaulOTron2000 Apr 28 '14 at 17:44
  • 5
    SqlConnection should be garbage collected, yes, but internally the connection will stay open. So next time you call open, it just grabs an unused connection from the pool. More info: [SQL Server Connection Pooling (ADO.NET)](http://msdn.microsoft.com/en-us/library/8xx3tyca(v=vs.110).aspx). Regarding your code, you may find that `Using` approach looks nicer: http://stackoverflow.com/questions/16903289/using-for-idbconnection-idbtransaction-safe-to-use – Victor Zakharov Apr 28 '14 at 17:48
  • 3
    Best practice is to close each data connection as soon as possible. This means you should **not** establish a connection on launch and close it on shutdown as suggested in your second paragraph. You may find this [MSDN Best Practices article](http://msdn.microsoft.com/en-us/library/ms971481.aspx) useful; the section on Using Connections is about 2/3 down the page. I also agree with @Neolisk about `Using`. – FumblesWithCode Apr 28 '14 at 18:49
  • 1
    @PaulOTron2000 When the method exits, the SqlConnection is **not** automatically collected... at least not right away. Garbage collection is non-deterministic, such that the object may hang around for a while. Relying on Garbage Collection to close your connections can result in a denial of service situation, if enough instances of your app do this for long enough that you exhaust the number of connections supported by the database server, and effectively lock yourself out of the database. This is why connections should **always** be closed as part of a `Finally` block. – Joel Coehoorn Apr 28 '14 at 19:38

2 Answers2

3

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.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • Good stuff, thank you very much Joel! For context, I was once a "real" developer. (Or at least I was able to trick people into paying me to do it.) I worked with VB3 through VB6. (I'm old.) I was always the front end guy... I made kiosks to present information on touch screens. My job was mostly to make stuff pretty. I learned only basic SQL queries and graphics, letting other folks set up the connections. Today I find my skills are useful as I'm making something for a business I own in an unrelated field. Hope to sell it to competitors. – PaulOTron2000 Apr 28 '14 at 20:09
0

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)
PaulOTron2000
  • 1,115
  • 1
  • 13
  • 20