VB.net connection string works, then doesn't, then does - why? - vb.net

Using VB.NET, VS 19 v16.10.4
I have a small application which asks a user to provide database server, database name and then builds a connection string. Then, using a data access layer DLL I've written it runs a check to see if a connection can be made to the database.
The problem is unusual:
If I write the connection string direct to the data access layer into a variable it connects;
If I use a string builder to build the string then pass it to the data access layer it fails;
If I write the connection string into a variable then pass it to the data access layer it fails;
If I use the first step again it works.
With DAL
MessageBox.Show("Using hard-coded string")
.ConnectionString = "data source=THEWINELIBRARY\MSSQLSERVER01;initial catalog=TrialDatabase;trusted_connection=true"
.DoConnectionCheck() 'THIS WORKS
MessageBox.Show("Using string builder string")
.ConnectionString = SBConnString.ToString.Trim
.DoConnectionCheck() 'THIS FAILS
MessageBox.Show("Using CS string")
.ConnectionString = CS.Trim
.DoConnectionCheck() 'THIS FAILS
MessageBox.Show("Using hard-coded string")
.ConnectionString = "data source=THEWINELIBRARY\MSSQLSERVER01;initial catalog=TrialDatabase;trusted_connection=true"
.DoConnectionCheck() 'THIS WORKS
End With
The data access layer does the following in the DoConnectionCheck method:
Public Sub DoConnectionCheck()
OpenConnection()
With AppConnection
If (.State = ConnectionState.Open) Then
RaiseEvent ConnectionOpened()
CloseConnection()
End If
End With
End Sub
and the OpenConnection method:
Private Sub OpenConnection()
With AppConnection
'Is the conenction currently closed?
If (.State = ConnectionState.Closed) Then
Try
'Set connection string to POS
.ConnectionString = ConnectionString
'Try opening the connection
.Open()
'Otherwise raise an event.
Catch E As Exception
RaiseEvent ConnectionFailed()
End Try
End If
End With
End Sub
And the connection string is a simple property:
Public ConnectionString As String
So, what I don't understand is why the connection fails with the string builder string and the variable string, but not with the hard-coded string?
If there is any other code you need please let me know, I posted what i think is enough to explain the problem without putting in too much that makes the question unreadable.
here is the code which constructs the connection string from entries in a form:
Dim CS As String = "datasource=" & .TextServer.Text.Trim & ";initial catalog=" & .TextDatabase.Text.Trim & ";trusted_connection=true"
SBConnString = New StringBuilder
With SBConnString
.Clear()
.Append("datasource=")
.Append(Me.TextServer.Text.Trim)
.Append(";initial catalog=")
.Append(Me.TextDatabase.Text.Trim)
.Append(";trusted_connection=true")
End With
When these strings are compared to the hard coded string
DAL.ConnectionString = "data source=THEWINELIBRARY\MSSQLSERVER01;initial catalog=TrialDatabase;trusted_connection=true"
the function returns a value 1, "The first substring follows the second substring in the sort order." according to the documentation.
As far as I can see, these strings are identical but somewhere an additional character must be getting inserted in the form textbox.
Again, any suggestions gratefully received.
All the best,
Dermot

Mea culpa - I had a very simple mistake which I could not see - in the two strings causing the problem I had datasource rather than date source...

Related

How can I change the command text of an SQL connected table in Excel using VBA? [duplicate]

I have an Excel document that has a macro which when run will modify a CommandText of that connection to pass in parameters from the Excel spreadsheet, like so:
Sub RefreshData()
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary")
.OLEDBConnection.CommandText = "Job_Cost_Code_Transaction_Summary_Percentage_Pending #monthEndDate='" & Worksheets("Cost to Complete").Range("MonthEndDate").Value & "', #job ='" & Worksheets("Cost to Complete").Range("Job").Value & "'"
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").Refresh
End Sub
I would like the refresh to not only modify the connection command but also modify the connection as I would like to use it with a different database also:
Just like the macro replaces the command parameters with values from the spreadsheet I would like it to also replace the database server name and database name from values from the spreadsheet.
A complete implementation is not required, just the code to modify the connection with values from the sheet will be sufficient, I should be able to get it working from there.
I tried to do something like this:
ActiveWorkbook
.Connections("Job_Cost_Code_Transaction_Summary")
.OLEDBConnection.Connection = "new connection string"
but that does not work. Thanks.
The answer to my question is below.
All of the other answers are mostly correct and focus on modifying the current connection, but I want just wanting to know how to set the connection string on the connection.
The bug came down to this. If you look at my screenshot you will see that the connection string was:
Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=True;Initial Catalog=ADCData_Doric;Data Source=doric-server5;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=LHOLDER-VM;Use Encryption for Data=False;Tag with column collation when possible=False
I was trying to set that string with ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").OLEDBConnection.Connection = "connection string"
I was getting an error when i was simply trying to assign the full string to the Connection. I was able to MsgBox the current connection string with that property but not set the connection string back without getting the error.
I have since found that the connection string needs to have OLEDB; prepended to the string.
so this now works!!!
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").OLEDBConnection.Connection = "OLEDB;Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=True;Initial Catalog=ADCData_Doric;Data Source=doric-server5;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=LHOLDER-VM;Use Encryption for Data=False;Tag with column collation when possible=False"
very subtle but that was the bug!
I think you are so close to achieve what you want.
I was able to change for ODBCConnection. Sorry that I couldn't setup OLEDBConnection to test, you can change occurrences of ODBCConnection to OLEDBConnection in your case.
Try add this 2 subs with modification, and throw in what you need to replace in the CommandText and Connection String. Note I put .Refresh to update the connection, you may not need until actual data refresh is needed.
You can change other fields using the same idea of breaking things up then Join it later:
Private Sub ChangeConnectionString(sInitialCatalog As String, sDataSource As String)
Dim sCon As String, oTmp As Variant, i As Long
With ThisWorkbook.Connections("Job_Cost_Code_Transaction_Summary").ODBCConnection
sCon = .Connection
oTmp = Split(sCon, ";")
For i = 0 To UBound(oTmp) - 1
' Look for Initial Catalog
If InStr(1, oTmp(i), "Initial Catalog", vbTextCompare) = 1 Then
oTmp(i) = "Initial Catalog=" & sInitialCatalog
' Look for Data Source
ElseIf InStr(1, oTmp(i), "Data Source", vbTextCompare) = 1 Then
oTmp(i) = "Data Source=" & sDataSource
End If
Next
sCon = Join(oTmp, ";")
.Connection = sCon
.Refresh
End With
End Sub
Private Sub ChangeCommanText(sCMD As String)
With ThisWorkbook.Connections("Job_Cost_Code_Transaction_Summary").ODBCConnection
.CommandText = sCMD
.Refresh
End With
End Sub
You could use a function that takes the OLEDBConnection and the parameters to be updated as inputs, and returns the new connection string. It's similar to Jzz's answer but allows some flexibility without having to edit the connection string within the VBA code each time you want to change it - at worst you'd have to add new parameters to the functions.
Function NewConnectionString(conTarget As OLEDBConnection, strCatalog As String, strDataSource As String) As String
NewConnectionString = conTarget.Connection
NewConnectionString = ReplaceParameter("Initial Catalog", strCatalog)
NewConnectionString = ReplaceParameter("Data Source", strDataSource)
End Function
Function ReplaceParameter(strConnection As String, strParamName As String, strParamValue As String) As String
'Find the start and end points of the parameter
Dim intParamStart As Integer
Dim intParamEnd As Integer
intParamStart = InStr(1, strConnection, strParamName & "=")
intParamEnd = InStr(intParamStart + 1, strConnection, ";")
'Replace the parameter value
Dim strConStart As String
Dim strConEnd As String
strConStart = Left(strConnection, intParamStart + Len(strParamName & "=") - 1)
strConEnd = Right(strConnection, Len(strConnection) - intParamEnd + 1)
ReplaceParameter = strConStart & strParamValue & strConEnd
End Function
Note that I have modified this from existing code that I have used for a particular application, so it's partly tested and might need some tweaking before it totally meets your needs.
Note as well that it'll need some kind of calling code as well, which would be (assuming that the new catalog and data source are stored in worksheet cells):
Sub UpdateConnection(strConnection As String, rngNewCatalog As Range, rngNewSource As Range)
Dim conTarget As OLEDBConnection
Set conTarget = ThisWorkbook.Connections.OLEDBConnection(strConnection)
conTarget.Connection = NewConnectionString(conTarget, rngNewCatalog.Value, rngNewSource.Value)
conTarget.Refresh
End Sub
I would like to give my small contribute here to this old topic.
If you have many connections in your Excel file, and you want to change the DB name and DB server for all of them, you can use the following code as well:
It iterates through all connections and extracts the connection string
Each connection string is split into an array of strings
It iterates through the array searching for the right connection values to modify, the others are not touched
The it recompose the array into the string and commit the change
This way you don't need to use replace and to know the previous value, and the rest of the string will remain intact.
Also, we can refer to a cell name, so you can have names in your Excel file
I hope it can help
Sub RelinkConnections()
Dim currConnValues() As String
For Each currConnection In ThisWorkbook.Connections
currConnValues = Split(currConnection.OLEDBConnection.Connection, ";")
For i = 0 To UBound(currConnValues)
If (InStr(currConnValues(i), "Initial Catalog") <> 0) Then
currConnValues(i) = "Initial Catalog=" + Range("DBName").value
ElseIf (InStr(currConnValues(i), "Data Source") <> 0) Then
currConnValues(i) = "Data Source=" + Range("DBServer").value
End If
Next
currConnection.OLEDBConnection.Connection = Join(currConnValues, ";")
currConnection.Refresh
Next
End Sub
This should do the trick:
Sub jzz()
Dim conn As Variant
Dim connectString As String
For Each conn In ActiveWorkbook.Connections
connectString = conn.ODBCConnection.Connection
connectString = Replace(connectString, "Catalog=ADCData_Doric", "Catalog=Whatever")
connectString = Replace(connectString, "Data Source=doric-server5", "Data Source=Whatever")
conn.ODBCConnection.Connection = connectString
Next conn
End Sub
It loops every connection in your workbook and change the Connection String (in the 2 replace statements).
So to modify your example:
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").ODBCConnection.Connection = "new connection string"
I assume it is necessary for your to keep the same connection-name? Otherwise, it would be simplest to ignore it and create a new Connection.
You might rename the connection, and create a new one using the name:
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").Name = "temp"
'or, more drastic:
'ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").Delete
ActiveWorkbook.Connections.Add "Job_Cost_Code_Transaction_Summary", _
"a description", "new connection string", "command text" '+ ,command type
Afterwards, Delete this connection and reinstate the old connection/name. (I am unable to test this myself currently, so tread carefully.)
Alternatively, you might change the current connections SourceConnectionFile:
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").OLEDBConnection.SourceConnectionFile = "..file location.."
This typically references an .odc file (Office Data Connection) saved on your system that contains the connection details. You can create this file from the Window's Control Panel.
You haven't specified, but an .odc file may be what your current connection is using.
Again, I am unable to test these suggestions, so you should investigate further and take some precautions - so that you won't risk losing the current connection details.

ADODB Connection Timeout in VBA for Excel 2016 - how to check if a connection is still active?

I have developed a small Excel addin using VBA which connects directly to a database. I set up the connection via a DSN. The addin works wonderfully when opening it and going right at it. However, after a while the connection to the database seems to timeout. More precisely, a perfectly valid query returns an error when trying to open the recordset.
My code is something like this:
'Check Connection
If Not MakeConnectionToDB Then
'Connection failed
[do something]
Exit Function
End If
'Active connection - run the query!
If Rs Is Nothing Then Set Rs = New ADODB.Recordset 'make sure its an active object
If Not Rs.State = adStateClosed Then Rs.Close 'make sure its not full (throws error if a query was called before)
Rs.Open strSQLQuery, CON 'Run query
the rs.open statement fails if the application was open but not used for a while. This is despite the MakeConnectionToDB UDF, which looks something like this:
If Not ConIsActive Then 'If there is no active connection, make it so
If CON Is Nothing Then 'Connection may be inactive because the object dropped, or because it timed out, or any other reason - Only recreate the object if the former is the case
Set CON = New ADODB.Connection
End If
On Error Resume Next
CON.Open strCon 'Try to connect - on error resume statement in order to ignore a connection error, that will be caught below
On Error GoTo 0
Err.Clear
MakeConnectionToDB = ConIsActive 'This is where a connection error will be caught if it occurred
Else
MakeConnectionToDB = True 'connection is active already
End If
and ConIsActive looks like:
Private Function ConIsActive() As Boolean
'return TRUE if there is an active connection, false otherwise
Dim blnTemp As Boolean
blnTemp = False
If (Not (CON Is Nothing)) And (Not (CON = "")) Then If CON.State = adStateOpen Then blnTemp = True
ConIsActive = blnTemp
End Function
Basically, I check if the connection is open. My problem: All these checks return TRUE, but the connection isn't open at all. If I connect, then leave the application for a while, then get back to it, all the above will return that the connection is active, but when trying to open the recordset with a new query it will fail, presumably because the server closed the connection or something. I need to find a way to check if the connection is actually able to open a recordset.
Can I ping the server or something? How can I check if the database actually returns a result to my queries? Is there a way that has a higher performance than just sending a test query to the server combined with error handling on the recordset? I suppose that would work, but I need a high performance solution and I don't think doubling the number of queries for a simple connection check is a superior solution...
Any help is appreciated!
Your CON object seems to be globally-scoped, opened once, and then used everywhere in your code, and possibly closed at some point... or not.
Like every single object in any code base written in any language that supports objects, a database connection should be as short-lived as possible.
You open it, you do what you need to do with it, and then you close it. If you don't know what the next command is going to be executed against it and when, then the connection has no business remaining open.
Delete your global-scope CON. Kill it, with fire. A connection should be local to the function or procedure that uses it - it begins in that scope, and ends in that scope.
Or you can encapsulate it in your own object, if that makes things easier for you.
'#Folder("Data.SqlConnection")
Option Explicit
Private Const CONNECTION_STRING As String = "{CONNECTION STRING}"
Private mConnection As ADODB.Connection
Private Sub Class_Initialize()
Set mConnection = New ADODB.Connection
mConnection.Open
End Sub
Private Sub Class_Terminate()
mConnection.Close
Set mConnection = Nothing
End Sub
Public Sub ExecuteNonQuery(ByVal sql As String, ParamArray params())
With New ADODB.Command
Set .ActiveConnection = mConnection
Dim p As ADODB.Parameter
For Each p In params
.Paramaters.Append p
Next
.Execute
End With
End Sub
'...
An instance of that SqlConnection class should also be as short-lived as possible, but now most of the plumbing is abstracted away so your calling code can look like this:
Const sql As String = "exec dbo.LogUserIn #userName=?, #password=?;"
With New SqlConnection
Dim userName As ADODB.Parameter
Set userName = .CreateStringParameter(Environ$("USERNAME"))
Dim password As ADODB.Parameter
Set password = .CreateStringParameter(PromptForPassword)
.ExecuteNonQuery sql, userName, password
End With
The connection begins at New SqlConnection, cleanly ends at End With, and you can tweak that SqlClass as you need, to support transactions, and/or as illustrated above, to abstract away the parameter-creating boilerplate.
But the idea remains: you don't create a database connection and leave it dangling in global scope, not knowing whether some code somewhere might have set it to Nothing, or closed it, or started a transaction that was never committed, or God knows what.
Create
Open
Execute
Close
Always. As tightly-scoped as possible. Then you won't have any object lifetime issues.

VB.net insert .msg file into access database [duplicate]

I'm writing a VB application where I need to store an image in the database. The user selects the image on their computer, which gives me the path as a string. Here's my attempt at it, however I'm getting the error "An INSERT INTO query cannot contain a multi-valued field."
Here is my code:
Dim buff As Byte() = Nothing
Public Function ReadByteArrayFromFile(ByVal fileName As String) As Byte()
Dim fs As New FileStream(fileName, FileMode.Open, FileAccess.Read)
Dim br As New BinaryReader(fs)
Dim numBytes As Long = New FileInfo(fileName).Length
buff = br.ReadBytes(CInt(numBytes))
Return buff
End Function
Sub ....
Dim connImg As New OleDbConnection
Dim sConnString As String
Dim cmdImg As New OleDbCommand
sConnString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & My.Settings.DB & ";Persist Security Info=False;"
connImg = New OleDbConnection(sConnString)
connImg.Open()
cmdImg.Connection = connImg
cmdImg.CommandType = CommandType.Text
If d.slogo <> "" Then
cmdImg.CommandText = "INSERT INTO Logo ( refId, [type], [img] ) VALUES(#refId, #type, #imgBinary)"
cmdImg.Parameters.Add("#refId", OleDbType.Double).Value = refId
cmdImg.Parameters.Add("#type", OleDbType.Double).Value = 0
cmdImg.Parameters.Add("#imgBinary", OleDbType.VarBinary).Value = ReadByteArrayFromFile(PathToImage)
cmdImg.ExecuteNonQuery()
End If
....
End Sub
I've tried searching for other solutions online, but it seems everything I find is VB6 or VBA code. And I know people are going to argue that images should not be stored in the database, but in this case, it is my only option.
Thank-you for any help!
As you have discovered, you cannot use a SQL statement to insert files into an Attachment field in an Access database. You have to use the LoadFromFile() method of an ACE DAO Field2 object. The following C# code works for me. It is adapted from the Office Blog entry here.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.Access.Dao;
namespace daoConsoleApp
{
class Program
{
static void Main(string[] args)
{
// This code requires the following COM reference in your project:
//
// Microsoft Office 14.0 Access Database Engine Object Library
//
var dbe = new DBEngine();
Database db = dbe.OpenDatabase(#"C:\__tmp\testData.accdb");
try
{
Recordset rstMain = db.OpenRecordset(
"SELECT refId, img FROM Logo WHERE refId = 1",
RecordsetTypeEnum.dbOpenDynaset);
if (rstMain.EOF)
{
// record does not already exist in [Logo] table, so add it
rstMain.AddNew();
rstMain.Fields["refId"].Value = 1;
}
else
{
rstMain.Edit();
}
// retrieve Recordset2 object for (potentially multi-valued) [img] field
// of the current record in rstMain
Recordset2 rstAttach = rstMain.Fields["img"].Value;
rstAttach.AddNew();
Field2 fldAttach =
(Field2)rstAttach.Fields["FileData"];
fldAttach.LoadFromFile(#"C:\__tmp\testImage.jpg");
rstAttach.Update();
rstAttach.Close();
rstMain.Update();
rstMain.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
I did the same thing based off of the above code and the blog entry from here.
Here is my vb.net code which allows me to do an OpenFileDialog and multiple selections and the function will handle storing multiple files just fine. Though I did have to add a reference to my project for Microsoft Office 15.0 Access database engine Object Library to get it to work properly. I think you can go down to 12.0 or 14.0 as well.
Private Sub AddAttachment(ByVal Files() As String)
Const Caller = "AddAttachment"
Dim dbe = New Microsoft.Office.Interop.Access.Dao.DBEngine()
Dim db As Microsoft.Office.Interop.Access.Dao.Database
db = dbe.OpenDatabase(dbPath)
Try
Dim rstMain As Microsoft.Office.Interop.Access.Dao.Recordset
rstMain = db.OpenRecordset("SELECT ID, fieldName FROM tableName WHERE ID = " + (dt.Rows(currentRow).Item("ID").ToString), Microsoft.Office.Interop.Access.Dao.RecordsetTypeEnum.dbOpenDynaset)
If (rstMain.EOF) Then
rstMain.AddNew()
rstMain.Fields("ID").Value = 1
Else
For Each value As String In Files
rstMain.Edit()
Dim rstAttach As Microsoft.Office.Interop.Access.Dao.Recordset2
rstAttach = rstMain.Fields("ATTACHMENTS").Value
rstAttach.AddNew()
Dim fldAttach As Microsoft.Office.Interop.Access.Dao.Field2
fldAttach = rstAttach.Fields("FileData")
fldAttach.LoadFromFile(value)
rstAttach.Update()
rstAttach.Close()
Next
rstMain.Update()
rstMain.Close()
End If
Catch ex As Exception
If Err.Number <> 3820 Then
MsgBox(ex.Message)
Else
MsgBox("File of same name already attached", MsgBoxStyle.Critical, "Cannot attach file" & Caller)
MessageBox.Show(ex.Message)
End If
End Try
End Sub
I'm now working on the functions to RemoveAttachments and save files from the attachements field. I'll post those here later.
Another thing to add to this. If your database is encrypted then you will need to add to the OpenDatabase command.
This is the code I used in C# but the VB.NET code will be very similiar.
db = dbe.OpenDatabase(dbPath, false, false,"MS Access;PWD=password");
It took me ages to try and track this down on my own and I will try break down the various parts of the method. The MSDN Article for it can be found here.
1st Argument: dbPath, that's the same as the usage in the original post. The location and filename of the database you want to open.
2nd Argument: false. This is a true/false argument that if true opens the database in Exclusive-Mode. So that only this single program can use it. Most of the time this should be false. Only use exclusive mode if you have to.
3rd Argument: false. This is another true/false argument. This time if it's true then it opens the database in Read-only mode.This means you can only use recordsets to read information and you cannot use the Edit, Add or Delete methods. This can be true or false depending on your needs.
4th Argument: This sets specific properties in how to open the database. In this case. It says to set the Microsoft Access property 'PWD' to 'password'. In this property setting will tell method to open up an encrypted database using the password 'password'. Of course you will need to change "password" to whatever the database's actual password is to open but I'd been hunting this down for a while.
I hope this helps.

Creating a general SQL module in VB.NET

Hey everyone. I'm fairly new to VB.NET and I'm looking to create a module that will contain all general SQL functionality like connect, disconnect and execute a sql query etc.
I think I'm almost there but the code keeps bombing in one place.
Can anyone see what is wrong with the following code?
It bombs here, setting the command object to a connection object. The opening and closing of the connection works fine.
cmdSystem.Connection = cnSystem
Or maybe I'm just thinking old VB and am going about this all wrong.
Public Module modGeneral
Private cnSystem As New SqlClient.SqlConnection
Private cmdSystem As SqlClient.SqlCommand
Public Sub ConnectToSQL()
Dim sConnectionString As String = "Data Source=SQL;Initial Catalog=XXXX;User ID=XXXX;Password=XXXX"
Try
cnSystem.ConnectionString = sConnectionString
cnSystem.Open()
Catch ex As Exception
End Try
End Sub
Public Sub DisconnectFromSQL()
Try
cnSystem.Close()
cnSystem.Dispose()
Catch ex As Exception
End Try
End Sub
Public Function lExecuteSQL(ByVal sSQL As String) As Long
Dim lRecordsAffected As Long = 0
Try
cmdSystem.Connection = cnSystem
cmdSystem.CommandText = sSQL
lRecordsAffected = cmdSystem.ExecuteNonQuery()
cmdSystem.Dispose()
Catch ex As Exception
End Try
Return lRecordsAffected
End Function
End Module
Thanks in advance.
at some point, you need to instantiate your command object like you did the connection.
have you considered having these functions in a class instead of a module?

Importance of closing SQLConnection objects when using the SQLDataReader object

My present contract engagement is at a large E-Commerce company. Their code base which has origins going back to .Net 1.0 has caught me by surprise to contain many issues that raise the level of smell beyond the last crap I took.
That notwithstanding and trying to diffuse my level of distraction from it, I go along merrily trying to add in features to either fix other problems or extend more crap. Where I touch the DAL/BLL the time it will take to fix the aforementioned will be done. However I wanted to get a vote of confidence from the experts to get some assurance of not wasting the clients time or worse having my credibility voted down by touching "stuff that works". Of course unit testing would solve or at least soften this worry. Perhaps this should also be added to the wtf.com?
Public Function GetSizeInfoBySite(ByVal siteID As String) As IList
Dim strSQL As String = "YES INLINE SQL!! :)"
Dim ci As CrapInfo
Dim alAnArrayList As ArrayList
Dim cn As New SqlConnection(ConfigurationSettings.AppSettings("ConnectionString"))
Dim cmd As New SqlCommand(strSQL, cn)
cmd.Parameters.Add(New SqlParameter("#MySiteID", SqlDbType.NVarChar, 2)).Value = siteID
cn.Open()
Dim rs As SqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection)
While rs.Read()
ci = New CategoryInfo(rs("someID"), rs("someName"))
If IsNothing(alAnArrayList) Then
alAnArrayList = New ArrayList
End If
alAnArrayList.Add(ci)
End While
rs.Close()
Return CType(alAnArrayList, IList)
End Function
Does anyone see problems with this aside from the inline SQL which makes my gut churn? At the least wouldn't you ordinarily wrap the above in a try/catch/finally which most of us knows has been around since .Net v1.0? Even better would'nt it be wise to fix with Using statements? Does the SQLDataReader close really encapsulate the connection close automagically?
Nothing wrong with inline sql if the user input is properly parameterized, and this looks like it is.
Other than that, yes you do need to close the connections. On a busy web site you could hit your limit and that would cause all kinds of weirdness.
I also noticed it's still using an arraylist. Since they've moved on from .Net 1.0 it's time to update those to generic List<T>'s (and avoid the call to CType- you should be able to DirectCast() that instead).
Definitely get some using statements around the Connection and Reader objects. If there is an exception, they won't be closed until the Garbage Collector gets around to them.
I tend not to call .Close() when there are using statements. Even if the SqlDataReader closes the connection on dispose (check the doco), putting a using around the Connection can't hurt and sticks to the pattern .
If you do that the try/finally is only needed if you need to do exception handling right there. I tend to leave exception handling at the higher levels (wrap each UI entry point, Library entry points, extra info in exception) as the stacktrace is usually enough to debug the errors.
Not that it matters much, but if you are re-factoring, move the collection initialization outside the loop. On second thoughts the code returns null if there are no records.
At least SqlParameters are used! Get rid of anything that concatenates user input with SQL if you find it (SQL Injection attack) no matter how well "Cleaned" it is.
The connection will be closed when the reader is closed because it's using the CloseConnection command behavior.
Dim rs As SqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection)
According to MSDN (http://msdn.microsoft.com/en-us/library/aa326246(VS.71).aspx)
If the SqlDataReader is created with CommandBehavior set to CloseConnection, closing the SqlDataReader closes the connection automatically.
In reply to some of the great points indicated by Joel and Robert I refactored the method as follows which ran flawless.
Public Function GetSomeInfoByBusObject(ByVal SomeID As String) As IList
Dim strSQL As String = "InLine SQL"
Dim ci As BusObject
Dim list As New GenList(Of BusObject)
Dim cn As New SqlConnection(
ConfigurationSettings.AppSettings("ConnectionString"))
Using cn
Dim cmd As New SqlCommand(strSQL, cn)
Using cmd
cmd.Parameters.Add(New SqlParameter
("#SomeID", SqlDbType.NVarChar, 2)).Value = strSiteID
cn.Open()
Dim result As SqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection)
While result.Read()
ci = New BusObject(rs("id), result("description"))
list.Add(DirectCast(ci, BusObject))
End While
result.Close()
End Using
Return list
End Using
End Function
Created a nice little helper class to wrap the generic details up
Public Class GenList(Of T)
Inherits CollectionBase
Public Function Add(ByVal value As T) As Integer
Return List.Add(value)
End Function
Public Sub Remove(ByVal value As T)
List.Remove(value)
End Sub
Public ReadOnly Property Item(ByVal index As Integer) As T
Get
Return CType(List.Item(index), T)
End Get
End Property
End Class
If you were using c# I would wrap the datareader creation in a using statement but I don't think vb has those?
Public Function GetSizeInfoBySite(ByVal siteID As String) As IList(Of CategoryInfo)
Dim strSQL As String = "YES INLINE SQL!! :)"
'reference the 2.0 System.Configuration, and add a connection string section to web.config
' <connectionStrings>
' <add name="somename" connectionString="someconnectionstring" />
' </connectionStrings >
Using cn As New SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings("somename").ConnectionString
Using cmd As New SqlCommand(strSQL, cn)
cmd.Parameters.Add(New SqlParameter("#MySiteID", SqlDbType.NVarChar, 2)).Value = siteID
cn.Open()
Using reader As IDataReader = cmd.ExecuteReader()
Dim records As IList(Of CategoryInfo) = New List(Of CategoryInfo)
'get ordinal col indexes
Dim ordinal_SomeId As Integer = reader.GetOrdinal("someID")
Dim ordinal_SomeName As Integer = reader.GetOrdinal("someName")
While reader.Read()
Dim ci As CategoryInfo = New CategoryInfo(reader.GetInt32(ordinal_SomeId), reader.GetString(ordinal_SomeName))
records.Add(ci)
End While
Return records
End Using
End Using
End Using
End Function
You could try something like the above, the using statements will handle connection closing and object disposal. This is available when the class implements IDisposable. Also build and return your IList of CategoryInfo.