avoid checking for DataRow.IsDBNull on each column? - vb.net

My code is 2x longer than it would be if I could automatically set IsDBNull to "" or simply roll over it without an error.
This is my code:
Dim conn As New SqlConnection
conn.ConnectionString = Module1.DBConn2
Dim sqlCommand = New SqlCommand("SELECT * FROM table", conn)
conn.Open()
Dim sqlDataset As DataSet = New DataSet()
Dim sqlDataAdapter As SqlDataAdapter = New SqlDataAdapter(sqlCommand)
sqlDataAdapter.Fill(sqlDataset)
conn.Close()
For Each rs As DataRow In sqlDataset.Tables(0).Rows
If Not IsDBNull(rs("column")) Then
Response.Write(rs("column"))
Else
Response.Write("")
End If
Response.Write("some stuff to write")
If Not IsDBNull(rs("column2")) Then
Response.Write(rs("column2"))
Else
Response.Write("")
End If
Next
In that case I'd just like to type Response.Write(rs("column")) instead of the If statement, and if column IsDBNull then output an empty string.
How can I do this?
Many thanks in advance!

You could simply use String.Join and pass row.ItemArray:
For Each row As DataRow In sqlDataset.Tables(0).Rows
Response.Write(String.Join("", row.ItemArray))
Next
That works since DBNull.ToString returns an empty string.
If you want to address every column, you can use the strongly typed DataRowExtensions.Field method which supports nullables and return null/Nothing for string. Then you could use the null-coalescing operator (?? in C#, If in VB).
Dim rowInfo = String.Format("{0}{1}{2}",
If(row.Field(Of String)("Column1"), ""),
If(row.Field(Of String)("Column2"), ""),
If(row.Field(Of String)("Column3"), ""))
However, note that String.Format will convert null/Nothing to "" implicitely anyway, so the If is redundant and just fyi.
MSDN:
If the object specified by index is a null reference (Nothing in
Visual Basic), then the format item is replaced by the empty string
("").

Here's a one-liner:
Response.Write(rs.IsNull("column") ? "" : rs("column"));
or make it an extension method:
public string GetValueOrBlankString(this DataRow rs, string column)
{
return rs.IsNull(column) ? "" : rs(column).ToString();
}
then call it as:
Response.Write(rs.GetValueOrBlankString("column"));

Dataset Extensions give you a clean way of doing and it's also strongly typed. The type must match the column type in the database though. If the database column can be null, then use a nullable type like below. The null values become Nothing for the returned nullable type.
For Each rs As DataRow In sqlDataset.Tables(0).Rows
'If string, you can use this. Null becomes nothing for the string.
Response.Write(rs.field(of String)("column"))
'if it's another type
Response.Write(rs.field(of Integer?)("column"))
Next

Ceres's answer is probably the best given that it avoids any sort of null testing, but it's worth noting that the 'IIF' function would also work pretty well her. It's still going to do the test for null but it's much more compact than how Joe was originally doing it. Something like this should do the trick:
For Each rs As DataRow In sqlDataset.Tables(0).Rows
Response.Write( IIF( IsDBNull(rs("column")), "", rs("column") ) )
Next
What's neat with this is you can substitute the "" for whatever you want to output if the value is in fact null ( a nice little added bonus. )
Here's some info on the 'IIF' function for those who don't know what it is:
http://msdn.microsoft.com/en-ca/library/27ydhh0d(v=vs.71).aspx

Related

vb.net Interpolated Strings

I was chastised by a professional developer with a lot of years of experience for Hard Coding my DB name
OK I get it we sometimes carry our bad codding habits with us till we learn the correct way to code
I have finally learned to use Interpolated Strings (personal view they are not pretty)
My Question involves the two Sub's posted below GetDB runs first then HowMany is called from GetDB
Sorry for stating the obvious my reason is I think that NewWord.db gets declared in GetDB and works in HowMany without the same construction Just a Wild Guess
Notice NO $ or quotation used in HowMany
Both Sub's produce desired results
The question is Why don't both statements need to be constructed the same?
Public Sub HowMany()
'Dim dbName As String = "NewWord.db"
Dim conn As New SQLiteConnection("Data Source ='{NewWord.db}';Version=3;")
tot = dgvOne.RowCount ' - 1
tbMessage.Text = "DGV has " & tot.ToString & " Rows"
End Sub
Private Sub GetDB()
Dim str2 As String
Dim s1 As Integer
'Dim dbName As String = "NewWord.db"
Using conn As New SQLiteConnection($"Data Source = '{"NewWord.db"}' ;Version=3;")
conn.Open()
That second method is a ridiculous and pointless use of string interpolation. What could possibly be the point of inserting a literal String into a literal String? The whole point is that you can insert values determined at run time. That second code is equivalent to using:
"Data Source = '" & "NewWord.db" & "' ;Version=3;"
What's the point of that? The idea is that you retrieve your database name from somewhere at run time, e.g. your config file, and then insert that into the template String, e.g.
Dim dbName = GetDbNameFromExternalFile()
Using conn As New SQLiteConnection($"Data Source = '{dbName}' ;Version=3;")
Now the user can edit that external file to change the database name after deploying the application. How could they change the name in your code?
To be clear, string interpolation is just native language support for the String.Format method. You can see that if you make a mistake that generates an exception and the that exception will refer to the String.Format method. In turn, String.Format is a way to make code that multiple values into a long template easier to read than if multiple concatenation operators were used.
Having lots of quotes and ampersands makes code hard to read and error-prone. I've lost count of the number of times people miss a single quote or a space or the like in a String because they couldn't read there messy code. Personally, I'll rarely use two concatenation operators in the same expression and never three. I'll do this:
Dim str = "some text" & someVar
but I'll rarely do this:
Dim str = "some text" & someVar & "some more text"
and I'll never do this:
Dim str = "some text" & someVar & "some more text" & someOtherVar
Before string interpolation, I would use String.Format:
Dim str = String.Format("some text{0}some more text{1}", someVar, someOtherVar)
Nowadays, I'll generally use string interpolation:
Dim str = $"some text{someVar}some more text{someOtherVar}"
Where I may still use String.Format over string interpolation is if one value is getting inserted in multiple places and/or where the text template and/or the expressions are long so that I can break the whole thing over multiple lines, e.g.
Dim str = String.Format("some text{0}some more text{1}yet more text{0}",
someVar,
someOtherVar)
I have no idea what NewWord.db is so I made a class to represent it.
Public Class NewWord
Public Shared Property db As String = "The db Name"
End Class
HowMany is not a very good name for your sub. Try to use more descriptive names.
The first sub doesn't even use the connection. The connection string in that code is a literal string. It will not consider NewWord.db as a variable. You will not notice this because you never attempt to open the connection. In my version you check the connection string with a Debug.Print.
I changed the last line to use and interpolated string. It is not necessary to call .ToString on tot.
Private Sub DisplayGridCount()
Dim conn As New SQLiteConnection("Data Source ='{NewWord.db}';Version=3;")
Debug.Print(conn.ConnectionString)
Dim tot = DataGridView1.RowCount
TextBox1.Text = $"DGV has {tot} Rows"
End Sub
The second snippet starts off with 2 unused variables. I deleted them. Again, the Debug.Print to show the difference in the 2 strings.
Private Sub TestConnection()
Using conn As New SQLiteConnection($"Data Source = '{NewWord.db}' ;Version=3;")
Debug.Print(conn.ConnectionString)
'conn.Open()
End Using
End Sub
As to where to store connection strings see https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/protecting-connection-information and Where to store Connection String

Converting Empty String to NULL Double

I have spent much of the day trying to find an answer for this one. I figured it would be easy but nothing is specific to what I am trying to do. So I hope someone can help.
I am bringing values over from one system to another. I have a few fields that come over as strings but they need to go into the new system as DOUBLE. The problem occurs when there are empty strings ("") and it is trying to store this as double. I have tried EVERYTHING.
dbnull.value
Double.TryParse
CDBL()
Double?
VAL()
etc...cannot get this to work and I am not sure why.
so here is the code:
Dim specvertclr As String = dt23.Rows(0).ItemArray.GetValue(38).ToString()
then when I insert this into my database I get the error because the field type is NUMERIC and I am trying to insert ""
I did have this working:
Dim specvertclr As String
If dt23.Rows(0).ItemArray.GetValue(38).ToString() = "" Then
specvertclr = CStr(0)
Else
specvertclr = dt23.Rows(0).ItemArray.GetValue(38).ToString()
End If
But the problem with this is it inserts a 0 value and 0 is not the same as NULL. I want the NUMERIC field in my destination database (SQLCE) to be EMPTY when the string is empty from the source database.
Any help?
This is what I tried:
Dim specvertclr As Nullable(Of Double) = CType(dt23.Rows(0).ItemArray.GetValue(38).ToString(), Double?)
and then my insert is basic and the program throws an error before it even gets here. I know this part is correct.
Dim cmd2 As SqlCeCommand = conn.CreateCommand()
cmd2.CommandText = "Insert into [Attr_Bridge] ([VERTCLR]) VALUES (?)"
With cmd2.Parameters
.AddWithValue("P1", specvertclr)
end with
You need to check the content of your string before adding it to the database
Dim specvertclr = dt23.Rows(0).ItemArray.GetValue(38).ToString()
....
Dim cmd2 As SqlCeCommand = conn.CreateCommand()
cmd2.CommandText = "Insert into [Attr_Bridge] ([VERTCLR]) VALUES (#P1)"
With cmd2.Parameters
.Add("#P1", SqlDbType.Float).Value = If(String.IsNullOrEmpty(specvertclr), DBNull.Value, CType(specvertclr, Object))
This turned out to be the answer .... Thank You Arminius for your linked article that lead me to right answer ... Thank you Steve for your time and help!
Dim specvertclr As String = dt23.Rows(0).ItemArray.GetValue(38).ToString()
Dim oerrorspec As Object = System.DBNull.Value
If specvertclr <> Nothing Then oerrorspec = specvertclr
and then in the parameters section I used this
.Add(New SqlCeParameter("#P41", oerrorspec))
I came across this question because I had the same issue. I was creating an app and one of the inputs was optional and I didn't want users to worry about putting zero so they don't receive an error. So my question was how to convert "" string to double? I came up with the below:
Dim Nontaxable As Double
If txtNontaxable.Text IsNot "" Then
Nontaxable = CDbl(txtNontaxable.Text)
Else
txtNontaxable.Text = 0
End If
if the string isn't blank, it will be converted fine, if its blank, it will be replaced with zero, and then it will be converted fine. Hope this helps anyone had the same issue.

Is it possible to change the default decimal separator in float.ToString()?

I need to send data in string format to mysql. By default vb.net interprets 0.5 as 0,5 which MySql won't accept. I know I could write floatval.tostring.replace(",", ".") to make it fit but I was wondering if it was possible to make it more comfortable so that an implicit conversion from float to string would produce a dot instead of a comma?
EDIT: per request, current code
Public Sub InsertInto(Values As IEnumerable(Of String))
Dim ValStr As String = ""
For Each V In Values
ValStr &= "'" & V & "',"
Next
Dim Command = New MySqlCommand("INSERT INTO " & Table & " VALUES (" & ValStr.Substring(0, ValStr.Length - 1) & ");", Connection)
Command.ExecuteNonQuery()
End Sub
this method is a part of a mysql connection wrapper and the properties "Connection" and "Table" are preassigned.
My test code calls the function as follows:
dimdum.InsertInto({"DEFAULT", (0.5).ToString.Replace(",", "."), "here is text"})
the test table columns are auto iterating int as primary key, a float and a varchar
As I have saind in my comment above, I am afraid that you need to revise a lot of your code. As is you have a lot of problems, the worst is the Sql Injection that sooner or later you have to fix, but your try to convert everything in a string has also the drawback that the conversion of decimals, dates and other floating points values give more immediate troubles than the Sql Injection one.
There is only one way to get out and it is the use of parameterized queries. More code to write but after a while it is very straightforward.
So for example you should rewrite your code to something like this
Public Sub InsertInto(sqlText As String, Values As List(Of MySqlParameter))
Using Connection = New MySqlConnection(... connectionstring here (or a global variable ....)
Using Command = New MySqlCommand(sqlText, Connection)
Connection.Open()
If Values IsNot Nothing Then
Command.Parameters.AddRange(values.ToArray)
End If
Command.ExecuteNonQuery()
End Using
End Using
End Sub
and call it with this
Dim decValue As Decimal = 0.5
Dim strValue As String = "Test"
Dim dateValue As DateTime = DateTime.Today
Dim parameters = New List(Of MySqlParameter)()
parameters.Add(New MySqlParameter() With { .ParameterName = "#p1",
.DbType = MySqlDbType.Decimal,
.Value = decValue})
parameters.Add(New MySqlParameter() With {.ParameterName = "#p2",
.DbType = MySqlDbType.String,
.Value = strValue})
parameters.Add(New MySqlParameter() With {.ParameterName = "#p3",
.DbType = MySqlDbType.Date,
.Value = dateValue})
InsertInto("INSERT INTO youTable VALUES(#p1, #p2, #p3)", parameters)
Note that now InserInto is just a simple routine that receives the command text and the parameters expected by the text, add them to the command, opens the connection, executes everything and exits closing the connection.
Note also that, with a parameterized queries, your sql command is totally void of the mess caused by single quotes for strings, formatting rules for dates and the handling of the decimal point is nowhere in sight
(A side note. This INSERT INTO text suppose that your table has exactly three fields and you supply the values for all of them, if you want to insert only a subset of fields then you need to pass them to the method as a third parameter )
Specify CultureInfo:
Dim n As Single
Dim s As String
n = Math.PI
s = n.ToString("F2", New System.Globalization.CultureInfo("en-US"))
s will be "3.14", even if your computer is set for a different format.

Use IF() Null Coalesce in SQL Parameters

I am trying to use the If() Operator to coalesce values in a SQL Parameter but I am having trouble figured it out.
Dim First as String = Nothing
First = FirstName.Text
Using conn As New SqlConnection()
'Code omitted
.AddWithValue("#FirstName", If(First, DBNull.Value))
Basically, if First = Nothing, the Parameter should be formatted like:
.AddWithValue("#FirstName", DBNull.Value)
If FirstName.Text is not Nothing or Null then it would basically be like:
.AddWithValue("#FirstName", First)
Anyone have any pointers or have any suggestions?
I understand that First is carrying a value of "", I am just not sure how to program around that. That's why I tried = Nothing.
This is currently being written like:
If employeeName <> "last,first" Then
cmd.Parameters.AddWithValue("#EMPL_NM", employeeName)
Else
cmd.Parameters.AddWithValue("#EMPL_NM", DBNull.Value)
End If
Which I'm trying to reduce to
.AddWithValue("#FirstName", If(First, DBNull.Value))
DBNull.Value and String are different types. The null-coalescing operator doesn't like that.
You could cast it to Object to make it compile, although i would prefer your If-Else:
cmd.Parameters.AddWithValue("#FirstName", If(First, DirectCast(DBNull.Value, Object)))

"System.NullReferenceException" when trying to fill array

This is my code:
Public UserBookings() As String
If dr.Read() Then
Dim intCounter As Integer
intCounter = 0
While dr.Read()
UserBookings(intCounter) = dr(0)
intCounter = intCounter + 1
End While
Return UserBookings
Else
Return False
End If
But I get this error at runtime:
A first chance exception of type 'System.NullReferenceException' occurred in Project.exe
And the array doesn't even seem to fill up.
Anything I can do to fix this? Bear in mind it is a dynamic array (I'm not sure how many entries it will have) so I don't see how I can loop through every entry and give it an initial value.
Thanks.
Among the problems listed as possible answers here, I also see that you are returning False at one point when return type of the method is string. That is not allowed. You can return Nothing or String.Empty, but not False.
It seems as though you dr(0) is an empty object, but try adding .ToString() to the end of it. But of course it depends on what datatype UserBookings() is.
Upon speaking with my colleague
you should probably use dr.HasRows instead of "if dr.read()" because you are losing an entire row.
The easiest option here is to realize that you can use an alternative construct such as an ArrayList (or better, a List(of String))
This way, the object will dynamically grow until you've loaded all the items, and you can return the result as an array
Two possible options below (the generic List(Of x) is preferred.
Function ReadArray(ByVal dr As IDataReader) As String()
Dim tempstorage As New List(Of String)
While dr.Read()
tempstorage.Add(dr(0))
End While
Return tempstorage.ToArray()
End Function
--
Function ReadArray2(ByVal dr As IDataReader) As String()
Dim tempstorage As New ArrayList()
While dr.Read()
tempstorage.Add(dr(0))
End While
Return tempstorage.ToArray(GetType(String))
End Function
--
Note: I've added some additional code (assuming that dr is a DataReader, and that you're calling this as a function