Disposing of managed objects - vb.net

Please have a look at the code below:
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
Dim objCommand As SqlCommand
Dim objCon As SqlConnection
Dim p1 As Person
Try
p1 = New Person
p1.DoSomething()
objCommand = New SqlCommand
Using objCommand
Dim strConString As String = "Data Source=IANSCOMPUTER;Initial Catalog=Test;Integrated Security=True"
objCon = New SqlConnection
Using objCon
objCon.ConnectionString = strConString
objCon.Open()
objCommand.Connection = objCon
objCommand.CommandText = "select startdate from person "
Dim objDR As SqlDataReader = objCommand.ExecuteReader
If objDR.HasRows Then
objDR.Read()
Using objCon
Dim startdate As String = objDR("startdate")
End Using
End If
End Using
End Using
Catch ex As Exception
Throw
Finally
If objCon.State = ConnectionState.Open Then
objCon.Close()
End If
objCon = Nothing
objCommand = Nothing
p1=Nothing 'This line is still needed
End Try
End Sub
I understand that the code in the finally clause is pointless because the connection and command are wrapped in Using statements.
However what happens if you have your own custom classes like Person, which doesn't use unmanaged resources? Surely the FINALLY clause would be needed in this instance to ensure that the reference to the object (on the heap) is set to nothing regardless of whether an exception is thrown or not?

When objects are referenced from a method, there is no need to set the variables to Nothing, since the objects will be "unrooted" and available for garbage collection as soon as the method call ends. When the method call ends all the local variables on the stack will be gone and the object they refer to will have no rooted references to them, making them available for garbage collection. In general you don't need to "null out" (by setting them to Nothing in vb.net) variables in .NET, since it does not rely on reference counting for managing objects on the heap.
Have a look at this article for an overview of how allocation and deallocation of memory works in .NET.

Related

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

DataAdapter is disposed before reaching "End Using"

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.

how to populate items from database in a listbox in vb.net

I was developing an application using oop concept.I have a class that has 2 attributes and have Get and Set methods namely WorkItemNumber and Description.
On the client side i have a list box used to populate the work items based on their description.Here's the code i wrote in the class o read items from the database.
Public Sub LoadWorkItem()
' Load the data.
' Select records.
Dim oWorkItem As WorkItem = New WorkItem()
Dim conn As New OleDbConnection
Dim data_reader As OleDbDataReader
conn = oWorkItem.GetDbConnection()
Dim cmd As New OleDbCommand("SELECT * FROM work_item ORDER BY [work item number]", conn)
data_reader = cmd.ExecuteReader()
'ListBox1.Items.Clear()
If data_reader.HasRows = True Then
Do While data_reader.Read()
WorkItemNumber = data_reader.Item("work item number")
Description = data_reader.Item("description")
Loop
End If
data_reader.Close()
data_reader = Nothing
cmd.Dispose()
cmd = Nothing
conn.Close()
conn.Dispose()
End Sub
How do i populate the listbox using the code,and if there's any improvement on the code please do tell me as well.Thank you
To poulate your ListBox, do this...
ListBox1.Item.Clear()
If data_reader.HasRows Then
Do While data_reader.Read()
WorkItemNumber = data_reader.Item("work item number")
Description = data_reader.Item("description")
ListBox1.Items.Add(New ListItem(Description, WorkItemNumber)
Loop
End If
As far as improvements, start by using a Using statement for the DB connection. In your code, if there is an exception while the database connection is open, it will never get closed. This is better...
Using conn As OleDbConnection = oWorkItem.GetDbConnection()
' Execute SQL and populate list...
End Using
The above code assures that your connection will be closed.
Then, turn on Option Strict and Option Explicit. This will force you to declare the Type for Description and WorkItemNumber and cast them as Strings when adding a ListItem. This will reduce run-time errors.
Finally, if this is anything but a small app you are doing as a learning experiment, you should read up on tiered application design. Your code is mixing UI, business logic, and data access in the same method. This is generally frowned upon.
Your "user interface" LoadWorkItem() method should ask a "core" method for a list of WorkItems.
Your core method should then ask a "data access" method for data.
The "data access" method should make the call to the database.
Happy coding.
Update: You can find excellent info about n-Tier architecture on MSDN. A good book to read once you grasp the fundamentals and have some confidence in .NET is Visual Basic .NET Business Objects.
Imports System.Data.SqlClient 'Reference The Sql Client
Public Class Form1
''Make sure to change the connection string below to your connection string this code only works for SQL DataBase. If Your connection String is wrong This will Not Work
Dim connString As String = "Data
Source=NameofYourSQLServer\SQLEXPRESS;Initial Catalog=NameOfYourDataBase;Integrated Security=True"
Dim tblDIV As DataTable
Dim daDIV As SqlDataAdapter
Dim dsDIV As New DataSet
Dim oCon As SqlConnection
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim oCon = New SqlConnection
oCon.ConnectionString = connString
dsDIV = New DataSet
' Select all Fields and order by ID or Replace * with name of Field
daDIV = New SqlDataAdapter("SELECT * FROM NameOfYourTable ORDER BY Id DESC", oCon)
'*** Define command builder to generate the necessary SQL
Dim builder As SqlCommandBuilder = New SqlCommandBuilder(daDIV)
builder.QuotePrefix = "["
builder.QuoteSuffix = "]"
Try
daDIV.FillSchema(dsDIV, SchemaType.Source, "DIV")
daDIV.Fill(dsDIV, "DIV")
tblDIV = dsDIV.Tables("DIV")
ListBox1.DataSource = tblDIV
ListBox1.DisplayMember = "NameOfTheFieldYouWanttoDisplay"
Catch ex As Exception
MsgBox("Encountered an Error;" & vbNewLine & ex.Message)
oCon.Close()
End Try
End Sub

VB.NET - Multiple SQLDataReader's

I develop a lot in ASP.NET and I know that you can only open one SQLDataReader for each SQLConnection. However, this does not appear to be the case in VB.NET (form application) i.e. I have opened multiple SQLDataReaders for one connection object. Is this allowed in VB.NET?
If there is not an obvious answer to this then I will post some code.
Here is some code:
Public Function CheckActiveReviews()
Dim objCon As SqlConnection
Dim objCommand As SqlCommand, objCommand2 As SqlCommand
Dim objDR As SqlDataReader, objDR2 As SqlDataReader
Try
objCon = New SqlConnection("Data Source=TestDatabase;Initial Catalog=TestTable;User ID=TestUser;Password=TestPassword;MultipleActiveResultSets=True")
objCommand = New SqlCommand
objCommand.Connection = objCon
objCommand2 = New SqlCommand
objCommand2.Connection = objCon
objCon.Open()
objCommand.CommandText = "SELECT ID FROM Person WHERE PersonID > 1000"
objDR = objCommand.ExecuteReader()
Do While objDR.Read
objCommand2.CommandText = "SELECT * FROM Sport WHERE PersonID = #PersonID "
objCommand2.Parameters.AddWithValue("#PersonID", objDR("ID"))
objDR2 = objCommand2.ExecuteReader
Loop
Catch ex As Exception
End Try
End Function
You can use multiple data readers if you use MARS - Multiple Active Result Sets - but I wouldn't advise that unless you really need it.
Instead, I'd suggest creating a new SqlConnection object each time you need it - use it for as short a period as you can, then dispose of it (use a Using statement to do this for you). The connection pool will take care of the efficiency in terms of reusing "physical" network connections where possible. That way you don't need to worry about whether the SqlConnection is already open etc - you just always follow the same "create, open, use, dispose" pattern every time.

Why do I got error when I create 2 commands on SQL in vb.net

Should I use using?
Private Sub btntest_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btntest.Click
If sqlConnection.State = ConnectionState.Closed Then
sqlConnection.Open()
End If
Dim query = "Select * from tablebusiness"
Dim cmd = New MySqlCommand(query, sqlConnection)
Dim data = cmd.ExecuteReader()
Do While data.Read
Loop
Dim cmd1 = New MySqlCommand(query, sqlConnection)
Dim data1 = cmd1.ExecuteReader //Error. Already have data reader
//Error There is already an open DataReader associated with this Connection which must be closed first.
Dim check = 1
'sqlConnection.Close()
End Sub
Although you have not let us know what the error is (which makes solving any problem much harder), I expect the issue is arising because you are trying to re-use the SqlConnection object for 2 different commands. Especially since you are not disposing your first command before initialising the second.
Firstly, use 2 different SqlConnection objects to manage the connection to the database. You are not putting any more overhead on the database or the code if you do this. Let the .NET framework connection pooling do its job - don't try to do it yourself. You don't need to do anything specific to enable connection pooling (although you can disable it by setting Pooling=false in your connection string).
Secondly use the using statement to correctly dispose your SqlConnection, SqlCommand, and SqlDataReader objects. e.g.
Using connection As New SqlConnection(connectionString)
connection.Open()
Using Command As New SqlCommand(query, connection)
Using reader As SqlDataReader = Command.ExecuteReader()
While reader.Read()
'Do Stuff'
End While
End Using
End Using
connection.Close()
End Using
You have missed the parenthesis after cmd1.ExecuteReader. It should be cmd1.ExecuteReader().
You Need another Conncetion if you want both the datareaders to work simultaneously, else close/ dispose the previous command before using cmd1.ExecuteReader()