DataAdapter is disposed before reaching "End Using" - vb.net

I know that I should always dispose DataAdapter instances. In most cases I'm disposing it immediately after closing the connection, but in cases like when user will be modifying DataTable items (displayed in ListBox or DataGridView) I create the DataAdapter, use it to fill the DataTable, but don't dispose it until the user clickes Save which calls DataAdapter.Update(DataTable)... not my main question but is this the right approach?
Back to the main question, I have these two functions:
Public Function LoadCompaniesDT(ByRef dtCompanies As DataTable) As Boolean
Using daCompanies As MySqlDataAdapter = Nothing
Return LoadCompaniesDT(daCompanies, dtCompanies)
End Using
End Function
Public Function LoadCompaniesDT(ByRef daCompanies As MySqlDataAdapter, ByRef dtCompanies As DataTable) As Boolean
Dim sql As String = "SELECT * FROM companies"
Return LoadDT(daCompanies, dtCompanies, sql, Res.CompaniesFailedMsgBody)
End Function
They're used to call LoadDT which fills the DataTable so I have the choice to pass a DataAdapter or not.
Now I'm confused about something: When using the first LoadCompaniesDT function, daCompanies is disposed before reaching End Using.. like this:
Public Function LoadCompaniesDT(ByRef dtCompanies As DataTable) As Boolean
Using daCompanies As MySqlDataAdapter = Nothing
Dim tmp As Boolean = LoadCompaniesDT(daCompanies, dtCompanies)
Console.WriteLine(daCompanies Is Nothing) ' ==> True!!
Return tmp
End Using
End Function
Note: if I use Dim daCompanies instead of Using daCompanies then daCompanies Is Nothing will return False.
LoadDT function code:
Private Function LoadDT(ByRef da As MySqlDataAdapter, ByRef dt As DataTable,
ByVal sqlQuery As String,
ByVal errorText As String) As Boolean
Dim connStr As String = String.Format("server={0}; port={1}; user id={2}; password={3}; database={4}",
DbServer, DbServerPort, DbUserName, DbPassword, DatabaseName)
Dim conn As MySqlConnection = New MySqlConnection(connStr)
Dim cmd As MySqlCommand = New MySqlCommand
Try
conn.Open()
cmd.CommandType = CommandType.Text
cmd.CommandText = sqlQuery
cmd.Connection = conn
da = New MySqlDataAdapter(cmd)
dt = New DataTable
da.Fill(dt)
Return True
Catch ex As Exception
MessageBox.Show(errorText, Res.ServerError, MessageBoxButtons.OK, MessageBoxIcon.Error)
Return False
Finally
cmd.Dispose()
cmd = Nothing
conn.Close()
conn.Dispose()
End Try
End Function

Update: you're right, you don't get an initialized MySqlDataAdapter back from the methods if the ByRef passed instance is used in a Using-statement. Those variables are readonly. In C# you get this meaningful compiler error:
Error CS1657 Cannot pass 'daCompanies' as a ref or out argument
because it is a 'using variable'
It's documented here:
Compiler Error CS1657
Cannot pass 'parameter' as a ref or out argument because 'reason''
This error occurs when a variable is passed as a ref or out argument
in a context in which that variable is readonly. Readonly contexts
include foreach iteration variables, using variables, and fixed
variables.
In VB.NET you can do that(so the compiler ignores it which is almost a bug) but the variable is not initialized afterwards. But as mentioned below, you should not use this approach anyway.
According to the the other question:
If you look at the sample on MSDN you'll see that microsoft also doesn't dispose the dataadapter. So it not really necessary. Having said that, it's always best practise to use the Using statement on anything that implements IDisposable.
A DataAdapter is not an expensive object and it does not hold unmanaged resources(like the connection). So it doesn't hurt to create a new instance from it whereever you need one. And you don't need to dispose it, but that's an implementation detail that might change in future or in a different implementation of DbDataAdapter, so it's still best practise to dispose it, best by using the Using-statement.
I wouldn't use your approach because you are passing the sql-string to the method which often leads to a sql injection vulnerabiliy. Instead use sql parameters.
For example:
Private Function LoadDT() As DataTable
Dim tbl As New DataTable()
'Load connection string from app.config or web.config
Dim sql As String = "SELECT * FROM companies" ' don't use * but list all columns explicitely
Using conn As New MySqlConnection(My.Settings.MySqlConnection)
Using da = New MySqlDataAdapter(sql, conn)
da.Fill(tbl)
End Using
End Using
Return tbl
End Function
Instead of passing an errorText ByRef i'd use a logging framework like log4net.

Related

Must declare the scalar variable "#SSTGroupID"

Private Function GetSvcType(ByVal oCommand As OleDbCommand, ByVal SSTGroupID As Integer) As DataTable
Dim sSQL As New StringBuilder
sSQL.AppendLine(" Select SSTServiceTypeID AS ID, SSTServiceTypeName AS Name ")
sSQL.AppendLine(" from fgen_SSTServiceType (nolock) ")
sSQL.AppendLine(" Where 1=1 AND Disabled = 0 ")
sSQL.AppendLine(" AND fgen_SSTServiceType.SSTGroupID = #SSTGroupID ")
oCommand.Parameters.AddWithValue("#SSTGroupID", SSTGroupID)
Return GetDataTable(sSQL.ToString)
End Function
Private Function GetDataTable(ByVal SQL As String) As DataTable
Dim oConn As OleDbConnection = New OleDbConnection(_strConnection)
Dim oCommand As New OleDbCommand("", oConn)
oCommand.Connection.Open()
oCommand.CommandText = SQL
oCommand.Parameters.Clear()
Dim oDataTable As New DataTable
Dim oDataAdapter As New OleDbDataAdapter(oCommand)
oDataAdapter.Fill(oDataTable)
If oDataTable.Rows.Count > 0 Then
GetDataTable = oDataTable
Else
GetDataTable = Nothing
End If
oCommand.Connection.Close()
oCommand.Dispose()
End Function
I've been searching for hours on end and can't seem to find a solution. I need your help please thanks
I've updated my question include the GetDataTable function. Please take a look thanks.
Your command never gets the text from the StringBuilder. So I think the missing link is that you should assign the string you've built to the command text
oCommand.CommandText = sSQL.ToString()
then add the parameter after that
Private Function GetSvcType(ByVal oCommand As OleDbCommand, ByVal SSTGroupID As Integer) As DataTable
Dim sSQL As New StringBuilder()
sSQL.AppendLine(" Select SSTServiceTypeID AS ID, SSTServiceTypeName AS Name ")
sSQL.AppendLine(" from fgen_SSTServiceType (nolock) ")
sSQL.AppendLine(" Where 1=1 AND Disabled = 0 ")
sSQL.AppendLine(" AND fgen_SSTServiceType.SSTGroupID = #SSTGroupID ")
oCommand.CommandText = sSQL.ToString()
oCommand.Parameters.AddWithValue("#SSTGroupID", SSTGroupID)
Return GetDataTable(oCommand.CommandText)
End Function
Alternatively, you may want to use a Using to create a command and dispose it. I'd write it but I don't see your connection so you should look into this answer for an example.
I wasn't sure what the Where 1 = 1 and the (no lock) were doing so I removed them.
The function FillDataTable contains all your database access code which keeps it separate from the User Interface code. Your database objects should be locale so you can control that they are closed and disposed. The Using...End Using block takes care of this even if there is an error. Get rid of any class level variables for commands and connections. Both the command and connection are included; note the comma at the end if the first line of the Using.
You can pass your connection string directly to the constructor of the connection and pass the command text and connection directly to the constructor of the command. Saves having to set these properties individually.
OleDb pays no attention to the name of the parameter, so, the order that the parameter is added to the Parameters collection must match the order that the parameter appears in the command text. In this case, you have only one but just for future reference. It is better to use the Parameters.Add() which includes the database datatype. See http://www.dbdelta.com/addwithvalue-is-evil/
and
https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/
and another one:
https://dba.stackexchange.com/questions/195937/addwithvalue-performance-and-plan-cache-implications
Here is another
https://andrevdm.blogspot.com/2010/12/parameterised-queriesdont-use.html
Note: I had to guess at the datatype of your parameter. Check your database for the actual type.
Always open your connection at the last possible moment (the line before the .Execute...) and close it as soon as possible (the End Using)
Private Function FillDataTable(GroupID As Long) As DataTable
Dim strSQL = "Select SSTServiceTypeID AS ID, SSTServiceTypeName As Name
From fgen_SSTServiceType
Where Disabled = 0
And SSTGroupID = #SSTGroupID "
Dim dt As New DataTable
Using cn As New OleDbConnection("Your connection string"),
cmd As New OleDbCommand(strSQL, cn)
cmd.Parameters.Add("#SSTGroupID", OleDbType.BigInt).Value = GroupID
cn.Open()
dt.Load(cmd.ExecuteReader)
End Using
Return dt
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim dt = FillDataTable(7L) 'the L indicates that this is a long,pass the GroupID to the function
DataGridView1.DataSource = dt
End Sub
EDIT
Dim dt = FillDataTable(7L) 'In the button code
And in the Data Access code change Oledb to Sql
Imports System.Data.SqlClient
Class DataAccess
Private Function FillDataTable(GroupID As Long) As DataTable
Dim strSQL = "Select SSTServiceTypeID AS ID, SSTServiceTypeName As Name
From fgen_SSTServiceType
Where Disabled = 0
And SSTGroupID = #SSTGroupID "
Dim dt As New DataTable
Using cn As New SqlConnection("Your connection string"),
cmd As New SqlCommand(strSQL, cn)
cmd.Parameters.Add("#SSTGroupID", SqlDbType.BigInt).Value = GroupID
cn.Open()
dt.Load(cmd.ExecuteReader)
End Using
Return dt
End Function
End Class

Syntax error in update statement using an MS Access database

I have inserted my record successfully with the OleDbCommand and parameters, but if I update the record I get the following error:
Syntax Error!
I have tried put the square brackets around the Field Name, and it still does not work. But if I copy the command to update with Access query, it works.
Code:
Public Function update() As Boolean
Dim STATE As Boolean = False
Dim cmd As New OleDbCommand
cmd.Connection = cn
cmd.CommandType = CommandType.Text
cmd.CommandText = "UPDATE [GUEST_DATA_TBL] SET [USD]=#USD, [RIEL]=#RIEL, [EURO]=#EURO, [BAHT]=#BAHT, [AUSD]=#AUSD, [GIFT]=#GIFT, [MEMO]=#MEMO WHERE [ID]=#ID"
AssignParams(cmd)
cmd.ExecuteNonQuery()
Return STATE
End Function
'the parameters
Private Sub AssignParams(cmd As OleDbCommand)
cmd.Parameters.AddWithValue("#ID", ID)
cmd.Parameters.AddWithValue("#USD", USD)
cmd.Parameters.AddWithValue("#RIEL", RIEL)
cmd.Parameters.AddWithValue("#BAHT", BAHT)
cmd.Parameters.AddWithValue("#EURO", EURO)
cmd.Parameters.AddWithValue("#AUSD", AUSD)
cmd.Parameters.AddWithValue("#GIFT", GIFT)
cmd.Parameters.AddWithValue("#MEMO", MEMO)
End Sub
First it's good to see that you are already using parameters but with MS Access, the order of the parameters is important not the names. I use the ? placeholder within my SQL command when using parameters. I also specify the data type so consider using the OleDbParameter Constructor (String, OleDbType) to add your parameters.
I would also consider implementing Using:
Managed resources are disposed of by the .NET Framework garbage collector (GC) without any extra coding on your part. You do not need a Using block for managed resources. However, you can still use a Using block to force the disposal of a managed resource instead of waiting for the garbage collector.
You could implement a check for the value returned by ExecuteNonQuery() to see how many rows were effected.
Lastly, with VB.NET by not specifying a modifier here; Private Sub AssignParams(cmd As OleDbCommand), the compiler by default will use ByVal:
Specifies that an argument is passed in such a way that the called procedure or property cannot change the value of a variable underlying the argument in the calling code.
You should be using ByRef:
Specifies that an argument is passed in such a way that the called procedure can change the value of a variable underlying the argument in the calling code.
Your code would look something like this:
Public Function update() As Boolean
Dim STATE As Boolean = False
Using con As OleDbConnection = cn,
cmd As New OleDbCommand("UPDATE [GUEST_DATA_TBL] SET [USD]=?, [RIEL]=?, [EURO]=?, [BAHT]=?, [AUSD]=?, [GIFT]=?, [MEMO]=? WHERE [ID]=?", con)
con.Open()
AssignParams(cmd)
Dim rowsAffected As Integer = cmd.ExecuteNonQuery()
If rowsAffected > 0 Then
STATE = True
End If
End Using
Return STATE
End Function
Private Sub AssignParams(ByRef cmd As OleDbCommand)
cmd.Parameters.Add("#USD", OleDbType.[Type]).Value = USD
cmd.Parameters.Add("#RIEL", OleDbType.[Type]).Value = RIEL
cmd.Parameters.Add("#EURO", OleDbType.[Type]).Value = EURO
cmd.Parameters.Add("#BAHT", OleDbType.[Type]).Value = BAHT
cmd.Parameters.Add("#AUSD", OleDbType.[Type]).Value = AUSD
cmd.Parameters.Add("#GIFT", OleDbType.[Type]).Value = GIFT
cmd.Parameters.Add("#MEMO", OleDbType.[Type]).Value = MEMO
cmd.Parameters.Add("#ID", OleDbType.[Type]).Value = ID
End Sub
Note that I have used OleDbType.[Type]. You will want to replace [Type] with the data type you've used on your database.
Private Sub AssignParams(cmd As OleDbCommand)
cmd.Parameters.AddWithValue("#USD", USD)
cmd.Parameters.AddWithValue("#RIEL", RIEL)
cmd.Parameters.AddWithValue("#EURO", EURO)
cmd.Parameters.AddWithValue("#BAHT", BAHT)
cmd.Parameters.AddWithValue("#AUSD", AUSD)
cmd.Parameters.AddWithValue("#GIFT", GIFT)
cmd.Parameters.AddWithValue("#MEMO", MEMO)
cmd.Parameters.AddWithValue("#ID", ID)
End Sub

Class 'System.DBNull' cannot be indexed because it has no default property

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

ExecuteNonQuery() for Insert

Can you please tell me what's wrong with this code?
Do I need to use DataAdapter to insert into a table?
I know the connectionString is ok, because I tested it on the Server Explorer.
Dim mydao As New Connection
Dim connectionString As String = mydao.GetConnectionString()
Dim connection As New SqlConnection(connectionString)
Dim cmd As New SqlCommand
Public Function add(ByVal area As String, ByVal user As String) As Integer
cmd.CommandText = "INSERT into Area (Area, user) VALUES ('" + area + "','" + user + "')"
Try
connection.Open()
Dim cant As Integer = cmd.ExecuteNonQuery()'it throws exception here
connection.Close()
Return cant
Catch ex As Exception
Console.WriteLine(ex.Message)
Return 0
End Try
End Function
The above code fails just after ExecuteNonQuery() and can´t figure why.
TARGET FIELDS (SQL Server 2008):
AREA varchar(100) NOT NULL ,
USER varchar(100) NOT NULL
The exception I receive is: Connection property has not initialized
There's a few issues with this code.
The most significant is that you aren't setting the Command's Connection property, so the command has no way of knowing how to connect to the database.
I would also strongly recommend utilizing using, and also parameterizing your query:
Finally, don't declare the connection and command outside of the function unless you need to. You should only keep the connection and command around for as long as you need them.
So your function would end up looking like:
Public Function add(ByVal area As String, ByVal user As String) As Integer
Dim mydao As New Connection
Using connection As New SqlConnection(mydao.ConnectionString())
Using command As New SqlCommand()
' Set the connection
command.Connection = connection
' Not necessary, but good practice
command.CommandType = CommandType.Text
' Example query using parameters
command.CommandText = "INSERT into Area (Area, user) VALUES (#area, #user)"
' Adding the parameters to the command
command.Parameters.AddWithValue("#area", area)
command.Parameters.AddWithValue("#user", user)
connection.Open()
Return command.ExecuteNonQuery()
End Using ' Dispose Command
End Using ' Dispose (and hence Close) Connection
End Function
Note that currently, you will be returning 0 all the time. Rather than having to check the value returned from the function, the above example will simply throw an exception. This makes for slightly cleaner code (as the caller would have to understand that 0 is an error condition), and, if you needed to handle the exception, simply wrap the call to this function in a Try-Catch block

VB.NET Returning an Object

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