In my VB.Net program I have found two (2) warnings shows the following messages:
Function doesn't return a value on all code paths. A null reference exception could occur at run time when the result is used
help me where is the problem and to fix it. The following snapshot is where the warning error indicates:
The Firs Warning near 'End Function'
'Executes SQL commands to the system database
Public Function ExecSQL(ByVal sql As String)
Dim com As New MySqlCommand(sql)
Try
RefreshConnection()
com.Connection = con
com.ExecuteNonQuery()
Catch ex As Exception
Return ex
MsgBox(ex.Message, MsgBoxStyle.Critical, "Error")
End Try
End Function
The Second Waring near 'End Function'
'Get the value of an specific field in a given sql string
Public Function GetField(ByVal sql As String, ByVal field As String)
Try
RefreshConnection()
Dim com As New MySqlCommand(sql, con)
Dim dReader As MySqlDataReader = com.ExecuteReader
While dReader.Read
GetField = dReader(field).ToString
End While
dReader.Close()
Catch ex As Exception
Return ex
MsgBox(ex.Message, MsgBoxStyle.Critical, "Error")
End Try
End Function
This occurs because in the exception handler you actually return the exception object ex. You're not returning anything if no exception occurs. You could explicitly add Return Nothing just above Catch ex as Exception, but I guess from the message that VB is doing that automatically for you...
By the way: What's the point returning the exception to the caller? Put the message box into the calling method and wrap your call in Try-Catch there. A method that's obviously not meant to interact with the user shouldn't show messages at all. And then you can just pass the exception up until you end up the presentation layer.
Long story short: No message boxes in business logic. User information only in presentation layer.
After reading your second method: Things are even worse here! If the data reader has rows, you set the result of the method to the last (!) result you found (this is a WTF in itself, but another story). If an error occurs, the result is an object of type Exception, so the calling code even needs to make sure to decide whether the result of the function call is actually a field value or an exception!
This is really bad design my friend.
You asked for fixed code:
'Executes SQL commands to the system database
Public Sub ExecSQL(ByVal sql As String)
Dim com As New MySqlCommand(sql)
RefreshConnection()
com.Connection = con
com.ExecuteNonQuery()
End Sub
'Get the value of an specific field in a given sql string
Public Function GetField(ByVal sql As String, ByVal field As String)
RefreshConnection()
Dim com As New MySqlCommand(sql, con)
Dim dReader As MySqlDataReader = com.ExecuteReader
GetField = Nothing
While dReader.Read
GetField = dReader(field).ToString
End While
dReader.Close()
End Function
There are several flaws here. In the interest of brevity, I'll just post some improved code:
'Executes SQL commands to the system database
Private Function ExecSQL(ByVal sql As String, ByVal params As IEnumerable(Of MySqlParameter))
Using cn As New MySqlConnection(GetConnectionString()), _
com As New MySqlCommand(sql, cn)
For Each param As MySqlParameter in params
com.Parameters.Add(param)
Next param
cn.Open()
com.ExecuteNonQuery()
End Using
End Function
'Get the value of an specific field in a given sql string
Private Function GetField(Of T)(ByVal sql As String, ByVal params As IEnumerable(Of MySqlParameter), ByVal field As String) As T
Using cn As New MySqlConnection(GetConnectionString())
com As New MySqlCommand(sql, cn)
For Each param As MySqlParameter In params
com.Parameters.Add(param)
Next param
cn.Open()
Using rdr As MySqlDataReader = com.ExecuteReader()
While rdr.Read()
Return CType(rdr(field), T)
End While
End Using
End Using
Return Nothing
End Function
Related
I am trying to update a record in MS Access using VB.net. This code will be under the "Delivered" button. When I try to run it, it shows the "No value given for one or more required parameters" error. Here is my code:
Private Const strConn As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\Traicy\Downloads\MWL(11-30-2021)\MadeWithLove\MadeWithLove\MadeWithLove.mdb;"
ReadOnly conn As OleDbConnection = New OleDbConnection(strConn)
Dim cmd As OleDbCommand
Public Sub DeliveredUpdate()
Const SQL As String = "UPDATE DELIVERY SET delivery_status = #status"
cmd = New OleDbCommand(SQL, conn)
' Update parameter
cmd.Parameters.AddWithValue("#status", "Delivered")
' Open connection, update, then close connection
Try
conn.Open()
If cmd.ExecuteNonQuery() > 0 Then
MsgBox("The delivery status was successfully updated.")
End If
conn.Close()
Catch ex As Exception
MsgBox(ex.Message)
conn.Close()
End Try
End Sub
Do not declare connections or commands outside of the method where they are used. These database objects use unmanaged resources. They release these resources in their Dispose methods. The language provides Using blocks to handle this.
As mentioned in comments by Andrew Morton, you should have a Where clause to tell the database which record to update. This would contain the primary key of the record. I guessed at a name for the field, OrderID. Check your database for the real field name.
Access does not use named parameters but you can use names for readability. Access will be able to recognize the parameters as long as they are added to the Parameters collection in the same order that they appear in the sql string. In some databases the Add method is superior to AddWithValue because it doesn't leave the datatype to chance.
It is a good idea to separate your database code from your user interface code. If you want to show a message box in your Catch put the Try blocks in the UI code. This way your function can be used in a web app or mobile app without rewriting.
Public Function DeliveredUpdate(ID As Integer) As Integer
Dim recordsUpdated As Integer
Dim SQL As String = "UPDATE DELIVERY SET delivery_status = #status Where OrderID = #Id;"
Using conn As New OleDbConnection(strConn),
cmd As New OleDbCommand(SQL, conn)
cmd.Parameters.Add("#status", OleDbType.VarChar).Value = "Delivered"
cmd.Parameters.Add("#Id", OleDbType.Integer).Value = ID
conn.Open()
recordsUpdated = cmd.ExecuteNonQuery
End Using 'closes and disposes the command and connection
Return recordsUpdated
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim retVal As Integer
Dim id As Integer = 1 'not sure where you are getting this value from
Try
retVal = DeliveredUpdate(id)
Catch ex As Exception
MsgBox(ex.Message)
End Try
If retVal > 0 Then
MsgBox("The delivery status was successfully updated.")
End If
End Sub
This is the function to access the database; the connection string is perfect - There is another Function similar to this and it works fine.
Friend Shared Function AddMember(member As Object) As Task(Of Integer)
Dim connection = New SqlConnection(Configuration.ConfigurationManager.ConnectionStrings("Carrel").ConnectionString)
Dim query = New SqlCommand("INSERT INTO JSON (CATEGORY,DATA) VALUES ('MEM',#JSONString)", connection)
Try
Connection.Open()
query.Parameters.Add(New SqlParameter("#JSONString", JsonConvert.SerializeObject(member)))
Return query.ExecuteNonQueryAsync()
Catch ex As Exception
MsgBox(ex.ToString())
Throw
Finally
query.Connection.Close()
End Try
End Function
Try this:
Friend Shared Async Function AddMember(member As Object) As Task(Of Integer)
Dim cn AS New SqlConnection(Configuration.ConfigurationManager.ConnectionStrings("Carrel").ConnectionString)
Dim query As New SqlCommand("INSERT INTO JSON (CATEGORY,DATA) VALUES ('MEM',#JSONString)", cn)
Try
cn.Open()
query.Parameters.Add(New SqlParameter("#JSONString", JsonConvert.SerializeObject(member)))
Return Await query.ExecuteNonQueryAsync()
Catch ex As Exception
MsgBox(ex.ToString())
Throw
Finally
query.Connection.Close()
End Try
End Function
This should prevent the method from continuing past the ExecuteNonQueryAsync() line until the task returns, so the connection will stay open, but can still run without blocking.
It's also better practice to put the connection and command object in Using blocks, and to not worry about exceptions at this level. This is a data access method... separate that from your presentation code. Let the exception bubble up in the calling code, especially in an Async method:
Friend Shared Async Function AddMember(member As Object) As Task(Of Integer)
Using cn AS New SqlConnection(Configuration.ConfigurationManager.ConnectionStrings("Carrel").ConnectionString), _
cmd As New SqlCommand("INSERT INTO JSON (CATEGORY,DATA) VALUES ('MEM',#JSONString)", cn)
query.Parameters.Add(New SqlParameter("#JSONString", JsonConvert.SerializeObject(member)))
cn.Open()
Return Await query.ExecuteNonQueryAsync()
End Using
End Function
Just be careful, because the async code could be hiding exceptions from you now.
The problem is that, as Aman Bachas said, you're calling ExecuteNonQueryAsync which will start the operation in the background. But then your Finally block will execute and close the connection before the execute actually happens.
What you'll need to do is call the non-Async version but then do your own Async so you can close the connection after it's done.
Public Function AddMember(member As Object) As Threading.Tasks.Task(Of Integer)
Dim connection = New SqlConnection(Configuration.ConfigurationManager.ConnectionStrings("Carrel").ConnectionString)
Dim query = New SqlCommand("INSERT INTO JSON (CATEGORY,DATA) VALUES ('MEM',#JSONString)", connection)
Try
connection.Open()
query.Parameters.Add(New SqlParameter("#JSONString", JsonConvert.SerializeObject(member)))
Return Threading.Tasks.Task.Factory.StartNew(Of Integer)(Function()
Dim ret = query.ExecuteNonQuery()
connection.Close()
Return ret
End Function)
Catch ex As Exception
MsgBox(ex.ToString())
Throw
End Try
End Function
You'll probably want to add some error handling inside the task and clean this up a bit, but that should give you a starting point.
Please replace ExecuteNonQueryAsync() with the ExecuteNonQuery().
Have some windows form with nice circural progress bar which i show user during long database function process. There is also task which calls function which inside i got query with transaction and there is catch implemented to rollback if it fails and return either true/false about state of process after finished. I didn't place there memssage box to show error as it's not adviced to store message boxes along in library projects so i would like to show this error on my task level function (in catch). However since i am catching error inside my function i am not able to show it (catch it) on the task's catch. How to accomplish that?
The only idea in my head is to instead of returning only result also return catched error message with use of Tuple like this: Tuple(Of Boolean, String). So i would be able to return two things: result and error message text. I am not so sure if this is right way to do such things. Looking for your advice.
This comes from windows forms project:
Dim pic As New CircuralWindowsForms(eCircularProgressType.Donut)
Dim tsk As Task(Of Boolean) = Task.Factory.StartNew(Of Boolean)(Function()
Dim resu = False
Try
resu = createArticle.ProcessArticle(data)
Catch sqlex As Exception
pic.Invoke(Sub() MessageBox.Show(pic, sqlex.Message))
Finally
'--Close form once done (on GUI thread)
pic.Invoke(New Action(Sub() pic.Close()))
End Try
Return resu
End Function)
'Show circural form
pic.ShowDialog()
Task.WaitAll(tsk)
if tsk.Result = true Then
...
This comes from library project:
Public Function ProcessArticle(data as Data) As Boolean
Dim result = false
Using connection As New SqlConnection(strcon)
Try
connection.Open()
Dim transaction As SqlTransaction
transaction = connection.BeginTransaction()
.....
transaction.Commit()
result=true
Catch ex As Exception
result = False
transaction.Rollback()
End Try
End Using
return result
End Function
Extended question a bit (discussion with sstan):
Public Sub DeleteAllRelated(varId As Integer)
Using con As New SqlConnection(strcon)
Dim commit As Boolean = True
con.Open()
Dim tran As SqlTransaction = con.BeginTransaction
Dim dt As New DataTable
dt = CType(New Variation_VariationAttributeDAL().GetAllByVariationId(varId), DataTable)
If dt IsNot Nothing Then
For Each row As DataRow In dt.Rows
If commit Then commit = commit And New Artikel_VariationDAL().DeleteByVariation_VariationAttribute(CInt(row(0)), tran)
Next
End If
If commit Then commit = commit And New Variation_VariationAttributeDAL().DeleteAllWhereVarId(varId, tran)
If commit Then commit = commit And Delete(varId, tran)
If commit Then commit = commit And New DALSubKategorie_Variation().Delete(varId, tran)
If commit Then
tran.Commit()
Else
tran.Rollback()
End If
End Using
End Sub
this is e.g for: If commit Then commit = commit And New DALSubKategorie_Variation().Delete(varId, tran)
Public Function Delete(varId As Integer, Optional transaction As SqlTransaction = Nothing) As Boolean
Dim result As Boolean = False
If transaction Is Nothing Then
Try
Using con As New SqlConnection(strcon)
Using cmd As New SqlCommand("DELETE FROM " & SharedData.Write.T(SharedData.Tables.SubKategorie_Variation) & " WHERE FK_Variation_ID=#FK_Variation_ID", con)
cmd.CommandType = CommandType.Text
cmd.Parameters.AddWithValue("#FK_Variation_ID", varId)
con.Open()
Dim rowsAffected As Integer = cmd.ExecuteNonQuery()
con.Close()
result = True
End Using
End Using
Catch ex as Exception
Throw
End Try
Else
Using cmd As New SqlCommand("DELETE FROM " & SharedData.Write.T(SharedData.Tables.SubKategorie_Variation) & " WHERE FK_Variation_ID=#FK_Variation_ID", transaction.Connection)
cmd.Transaction = transaction
cmd.CommandType = CommandType.Text
cmd.Parameters.AddWithValue("#FK_Variation_ID", varId)
Dim rowsAffected As Integer = cmd.ExecuteNonQuery()
result = True
End Using
End If
Return result
End Function
You're right that it would be wrong for your library to display message boxes directly. But that doesn't mean it should swallow exceptions either. In, fact, quite the opposite: you really should let the exception bubble up to the caller, and let the caller decide what to do with it.
With that in mind, I would change the ProcessArticle function to the following:
Public Sub ProcessArticle(data as Data)
Using connection As New SqlConnection(strcon)
Try
connection.Open()
Dim transaction As SqlTransaction
transaction = connection.BeginTransaction()
' .....
transaction.Commit()
Catch ex As Exception
transaction.Rollback()
Throw 'Rethrow exception. The caller can decide what to do with it.
End Try
End Using
End Sub
Notice how the exception is still caught to enable the transaction rollback, but the exception is rethrown so that the caller can catch it. This in turn means that you no longer need to return a boolean to indicate success or failure.
EDIT
Not directly related to your question, but I would further move the code around a little bit so that I don't accidentally try to rollback a transaction before it has even begun (With you current code, ask yourself what would happen if an error occurred while trying to open the connection?):
Public Sub ProcessArticle(data as Data)
Using connection As New SqlConnection(strcon)
connection.Open()
Using transaction = connection.BeginTransaction()
Try
' do work here
transaction.Commit()
Catch ex As Exception
transaction.Rollback()
Throw 'Rethrow exception. The caller can decide what to do with it.
End Try
End Using
End Using
End Sub
EDIT 2
More on the Throw Statement:
A Throw statement with no expression can only be used in a Catch statement, in which case the statement rethrows the exception currently being handled by the Catch statement.
EDIT 3: Simplified version of your last edit
Public Sub DeleteAllRelated(varId As Integer)
Using con As New SqlConnection(strcon)
con.Open()
Using transaction = connection.BeginTransaction()
Try
Dim dt As New DataTable
dt = CType(New Variation_VariationAttributeDAL().GetAllByVariationId(varId), DataTable)
If dt IsNot Nothing Then
For Each row As DataRow In dt.Rows
New Artikel_VariationDAL().DeleteByVariation_VariationAttribute(CInt(row(0)), tran)
Next
End If
New Variation_VariationAttributeDAL().DeleteAllWhereVarId(varId, tran)
Delete(varId, tran)
New DALSubKategorie_Variation().Delete(varId, tran)
'If we made it this far without an exception, then commit.
tran.Commit()
Catch ex As Exception
tran.Rollback()
Throw 'Rethrow exception.
End Try
End Using
End Using
End Sub
Public Sub Delete(varId As Integer, Optional transaction As SqlTransaction = Nothing)
If transaction Is Nothing Then
Using con As New SqlConnection(strcon)
Using cmd As New SqlCommand("DELETE FROM " & SharedData.Write.T(SharedData.Tables.SubKategorie_Variation) & " WHERE FK_Variation_ID=#FK_Variation_ID", con)
cmd.CommandType = CommandType.Text
cmd.Parameters.AddWithValue("#FK_Variation_ID", varId)
con.Open()
Dim rowsAffected As Integer = cmd.ExecuteNonQuery()
con.Close()
End Using
End Using
Else
Using cmd As New SqlCommand("DELETE FROM " & SharedData.Write.T(SharedData.Tables.SubKategorie_Variation) & " WHERE FK_Variation_ID=#FK_Variation_ID", transaction.Connection)
cmd.Transaction = transaction
cmd.CommandType = CommandType.Text
cmd.Parameters.AddWithValue("#FK_Variation_ID", varId)
Dim rowsAffected As Integer = cmd.ExecuteNonQuery()
End Using
End If
End Sub
EDIT 4: Example of exception chaining
Public Sub ProcessArticle(data as Data)
Try
' do work here
Catch ex As Exception
' If you want the original error to go up to the "upper levels"
' but with additional information, you need to throw a new
' instance of an exception with a new message that contains the additional information
' but you need to pass the original exception as a parameter to the constructor
' so that exceptions get chained together.
' If an "upper level" caller catches the chained exception,
' doing "ex.ToString" will provide all the information.
' Try it out, see how it works.
Throw New Exception("put your additional information here", ex)
End Try
End Sub
I have a function to get information on a SQL Server Table. I know the function is correct and returning everything as suposed too (I used msgBox to check it). The problem is when I try to use that function it displays an error:
My WCF Function:
Public Function GetCLInfo(ByVal cl_ID As Integer) Implements SQLOperator.GetCLInfo
Dim CLInfo(7) As String
Try
SQLCon.ConnectionString = SQLConString
SQLCon.Open()
SQLCMD = New SqlCommand("Select * from dbo.cl Where ID=" & cl_ID & ";", SQLCon)
SQLDataReader = SQLCMD.ExecuteReader()
While SQLDataReader.Read
CLInfo(0) = SQLDataReader("ID")
CLInfo(1) = SQLDataReader("Name")
CLInfo(2) = SQLDataReader("Address")
CLInfo(3) = SQLDataReader("Contact")
End While
Return CLInfo
Catch ex As Exception
MsgBox("Error: " & Environment.NewLine & ex.ToString)
CLInfo(0) = "False"
Return CLInfo
Finally
SQLCon.Close()
SQLCon.Dispose()
End Try
Return CLInfo
End Function
In the picture you can see aswell how I'm trying to use the function.
Can someone kindly tell me what am I doing wrong?
Your problem is that you are calling a function to return string array and program doesn't catch by itself. As #Matt Wilko point out use Option Strict On.
Second if you have more than one client your logic fails.
For I as integer = 1 to AllCl
Dim cl(7) as String
cl = Service.GetClInfo(I)
next
Above code will reset every each time, plus when you leave for loop you are loosing access to cl.
Ugly fix could be multidimensional array.
Now you should specify your return type
Public Function GetCLInfo(ByVal cl_ID As Integer) As String() Implements SQLOperator.GetCLInfo
Ok, here's my Code:
Imports IBM.Data.DB2.iSeries
Module ConsoleData1
Sub Main()
'program identifier (to verify that you're using the right module)
Console.WriteLine("ConsoleData1.vb")
'create the connection object to the IBM i
Using cn As New iDB2Connection("DataSource=xxxxxxxxxxxxx")
Try
cn.Open()
Catch ex As iDB2Exception
Console.WriteLine("An error occurred on cn.Open()")
Exit Sub
End Try
'create a command object, initialize to valid SQL statement
Using cmd As New iDB2Command("select * from testlib.finishk", cn)
'create a command object, initialize to valid SQL statement
Console.Write("Enter the Color Code!==>")
Dim baldue = Console.ReadLine()
Dim sql As String = "Select * from testlib.finishk where FINCOD >=" & FINCOD
Using cmdx As New iDB2Command(sql, cn)
End Using
'create a data reader object, fill by executing the command
Try
Using dr As iDB2DataReader = cmd.ExecuteReader
'display data in a table
Console.Write("FINCOD" & vbTab)
Console.Write("FINDES" & vbTab)
While dr.Read()
Console.Write("{0}{1}", dr("FINCOD"), vbTab)
Console.Write("{0}{1}", dr("FINDES"), vbTab)
End While
End Using
Catch ex As iDB2Exception
Console.WriteLine("An error occurred on cmd.ExecuteReader()")
Exit Sub
End Try
End Using
End Using
Console.WriteLine("Press ENTER to end")
Console.ReadLine()
End Sub
Sub HandleError(ByVal errorOccurred As String, ByVal ex As iDB2Exception)
Console.WriteLine(errorOccurred)
Console.WriteLine("Source: {0}", ex.Source)
Console.WriteLine("SQLState: {0}", ex.SqlState)
Console.WriteLine("Message: {0}", ex.Message)
Console.WriteLine("MessageCode: {0}", ex.MessageCode)
Console.WriteLine("MessageDetails: {0}", ex.MessageDetails)
Console.ReadLine()
End Sub
Private Function Screen() As Object
Throw New NotImplementedException
End Function
End Module
Now, how would I make it so that I can resize the cmd window to be larger and still retain the integrity of the table structure? If I resize the cmd window, it ends up looking like someone just threw the values into a window in random placement.
Thanks.
You need two things: a line break between each record and length information in your format string (to shoot for fixed width columns).
'You can play the -30 numbers here to get different spacing, based on the nature of your data
Dim recordFormat As String = "{0,-30} {1,-30}"
Console.WriteLine(recordFormat, "FINCOD","FINDES")
While dr.Read()
Console.WriteLine(recordFormat, dr("FINCOD"), dr("FINDES"))
End While
Finally, I'm not familiar with DB2, but please tell me there's a better way than string concatenation to add the user's FINDCOD value to the sql statement? What you're doing is horribly unforgivably insecure. What if I put the text 0;DROP TABLE testlib.finishk;-- into your system instead of a FINDCOD? Any sane code would use parameterized queries for this.