I want to generate some formatted output of data retrieved from an MS-Access database and stored in a DataTable object/variable, myDataTable. However, some of the fields in myDataTable cotain dbNull data. So, the following VB.net code snippet will give errors if the value of any of the fields lastname, intials, or sID is dbNull.
dim myDataTable as DataTable
dim tmpStr as String
dim sID as Integer = 1
...
myDataTable = myTableAdapter.GetData() ' Reads the data from MS-Access table
...
For Each myItem As DataRow In myDataTable.Rows
tmpStr = nameItem("lastname") + " " + nameItem("initials")
If myItem("sID")=sID Then
' Do something
End If
' print tmpStr
Next
So, how do i get the above code to work when the fields may contain dbNull without having to check each time if the data is dbNull as in this question?
The only way that i know of is to test for it, you can do a combined if though to make it easy.
If NOT IsDbNull(myItem("sID")) AndAlso myItem("sID") = sId Then
'Do success
ELSE
'Failure
End If
I wrote in VB as that is what it looks like you need, even though you mixed languages.
Edit
Cleaned up to use IsDbNull to make it more readable
I got tired of dealing with this problem so I wrote a NotNull() function to help me out.
Public Shared Function NotNull(Of T)(ByVal Value As T, ByVal DefaultValue As T) As T
If Value Is Nothing OrElse IsDBNull(Value) Then
Return DefaultValue
Else
Return Value
End If
End Function
Usage:
If NotNull(myItem("sID"), "") = sID Then
' Do something
End If
My NotNull() function has gone through a couple of overhauls over the years. Prior to Generics, I simply specified everything as an Object. But I much prefer the Generic version.
You can also use the Convert.ToString() and Convert.ToInteger() methods to convert items with DB null effectivly.
A variation on Steve Wortham's code, to be used nominally with nullable types:
Private Shared Function GetNullable(Of T)(dataobj As Object) As T
If Convert.IsDBNull(dataobj) Then
Return Nothing
Else
Return CType(dataobj, T)
End If
End Function
e.g.
mynullable = GetNullable(Of Integer?)(myobj)
You can then query mynullable (e.g., mynullable.HasValue)
Microsoft came up with DBNull in .NET 1.0 to represent database NULL. However, it's a pain in the behind to use because you can't create a strongly-typed variable to store a genuine value or null. Microsoft sort of solved that problem in .NET 2.0 with nullable types. However, you are still stuck with large chunks of API that use DBNull, and they can't be changed.
Just a suggestion, but what I normally do is this:
All variables containing data read from or written to a database should be able to handle null values. For value types, this means making them Nullable(Of T). For reference types (String and Byte()), this means allowing the value to be Nothing.
Write a set of functions to convert back and forth between "object that may contain DBNull" and "nullable .NET variable". Wrap all calls to DBNull-style APIs in these functions, then pretend that DBNull doesn't exist.
You can use the IsDbNull function:
If IsDbNull(myItem("sID")) = False AndAlso myItem("sID")==sID Then
// Do something
End If
If you are using a BLL/DAL setup try the iif when reading into the object in the DAL
While reader.Read()
colDropdownListNames.Add(New DDLItem( _
CType(reader("rid"), Integer), _
CType(reader("Item_Status"), String), _
CType(reader("Text_Show"), String), _
CType( IIf(IsDBNull(reader("Text_Use")), "", reader("Text_Use")) , String), _
CType(reader("Text_SystemOnly"), String), _
CType(reader("Parent_rid"), Integer)))
End While
For the rows containing strings, I can convert them to strings as in changing
tmpStr = nameItem("lastname") + " " + nameItem("initials")
to
tmpStr = myItem("lastname").toString + " " + myItem("intials").toString
For the comparison in the if statement myItem("sID")=sID, it needs to be change to
myItem("sID").Equals(sID)
Then the code will run without any runtime errors due to vbNull data.
VB.Net
========
Dim da As New SqlDataAdapter
Dim dt As New DataTable
Call conecDB() 'Connection to Database
da.SelectCommand = New SqlCommand("select max(RefNo) from BaseData", connDB)
da.Fill(dt)
If dt.Rows.Count > 0 And Convert.ToString(dt.Rows(0).Item(0)) = "" Then
MsgBox("datbase is null")
ElseIf dt.Rows.Count > 0 And Convert.ToString(dt.Rows(0).Item(0)) <> "" Then
MsgBox("datbase have value")
End If
I think this should be much easier to use:
select ISNULL(sum(field),0) from tablename
Copied from: http://www.codeproject.com/Questions/736515/How-do-I-avoide-Conversion-from-type-DBNull-to-typ
Hello Friends
This is the shortest method to check db Null in DataGrid and convert to string
create the cell validating event and write this code
If Convert.ToString(dgv.CurrentCell.Value) = "" Then
CurrentCell.Value = ""
End If
This is BY FAR the easiest way to convert DBNull to a string.
The trick is that you CANNOT use the TRIM function (which was my initial problem) when referring to the fields from the database:
BEFORE (produced error msg):
Me.txtProvNum.Text = IIf(Convert.IsDBNull(TRIM(myReader("Prov_Num"))), "", TRIM(myReader("Prov_Num")))
AFTER (no more error msg :-) ):
Me.txtProvNum.Text = IIf(Convert.IsDBNull(myReader("Prov_Num")), "", myReader("Prov_Num"))
Simple, but not obvious.
DbNull.Value.Equals(myValue)
I hate VB.NET
For your problem, you can use following special workaround coding that only exists in VB.Net.
Dim nId As Integer = dr("id") + "0"
This code will replace DBNull value contained in id column by integer 0.
The only acceptable default value is "0" because this expression must also be used when dr("id") is not NULL !
So, using this technic, your code would be
Dim myDataTable as DataTable
Dim s as String
Dim sID as Integer = 1
...
myDataTable = myTableAdapter.GetData() ' Reads the data from MS-Access table
...
For Each myItem As DataRow In myDataTable.Rows
s = nameItem("lastname") + " " + nameItem("initials")
If myItem("sID") + "0" = sID Then
' Do something
End If
Next
I have tested this solution and it works on my PC on Visual Studio 2022.
PS: if sID can be equal to 0 and you want to do something distinct when dr("sID") value is NULL, you must also adept you program and perhaps use Extension as proposed at end of this answer.
I have tested following statements
Dim iNo1 As Integer = dr("numero") + "0"
Dim iNo2 As Integer = dr("numero") & "0" '-> iNo = 10 when dr() = 1
Dim iNo3 As Integer = dr("numero") + "4" '-> iNo = 5 when dr() = 1
Dim iNo4 As Integer = dr("numero") & "4" '-> iNo = 14 when dr() = 1
Dim iNo5 As Integer = dr("numero") + "" -> System.InvalidCastException : 'La conversion de la chaîne "" en type 'Integer' n'est pas valide.'
Dim iNo6 As Integer = dr("numero") & "" -> System.InvalidCastException : 'La conversion de la chaîne "" en type 'Integer' n'est pas valide.'
Dim iNo7 As Integer = "" + dr("numero") -> System.InvalidCastException : 'La conversion de la chaîne "" en type 'Integer' n'est pas valide.'
Dim iNo8 As Integer = "" & dr("numero") -> System.InvalidCastException : 'La conversion de la chaîne "" en type 'Integer' n'est pas valide.'
Dim iNo9 As Integer = "0" + dr("numero")
Dim iNo0 As Integer = "0" & dr("numero")
Following statements works also correctly
Dim iNo9 As Integer = "0" + dr("numero")
Dim iNo0 As Integer = "0" & dr("numero")
I recognize that is a little tricky.
If trick are not your tips, you can also define an Extension so that following code works.
Dim iNo = dr.GetInteger("numero",0)
where GetInteger() code can be following
Module Extension
'***********************************************************************
'* GetString()
'***********************************************************************
<Extension()>
Public Function GetString(ByRef rd As SqlDataReader, ByRef sName As String, Optional ByVal sDefault As String = "") As String
Return GetString(rd, rd.GetOrdinal(sName), sDefault)
End Function
<Extension()>
Public Function GetString(ByRef rd As SqlDataReader, ByVal iCol As Integer, Optional ByVal sDefault As String = "") As String
If rd.IsDBNull(iCol) Then
Return sDefault
Else
Return rd.Item(iCol).ToString()
End If
End Function
'***********************************************************************
'* GetInteger()
'***********************************************************************
<Extension()>
Public Function GetInteger(ByRef rd As SqlDataReader, ByRef sName As String, Optional ByVal iDefault As Integer = -1) As Integer
Return GetInteger(rd, rd.GetOrdinal(sName), iDefault)
End Function
<Extension()>
Public Function GetInteger(ByRef rd As SqlDataReader, ByVal iCol As Integer, Optional ByVal iDefault As Integer = -1) As Integer
If rd.IsDBNull(iCol) Then
Return iDefault
Else
Return rd.Item(iCol)
End If
End Function
End Module
These methods are more explicitely and less tricky.
In addition, it is possible to define default values other than ZERO and also specific version as GetBoolean() or GetDate(), etc ...
Another possibility is to report SQL default conversion in SQL command using COALESCE SQL command !
Related
I have a function to which I want to pass an arbitrary number of paired parameters (i.e. a String variable and a second arbitrary type (could be a String, Integer etc.) - hence am declaring the second half of the pair as an Object. There could be one or more pairs of this nature.
The most obvious structure I could think of for this was therefore a Tuple(Of String, Object)
Here is the function :
Private Function TableLookup(
table As DataTable,
ByVal columnNamesAndKeys As List(Of Tuple(Of String, Object)),
resultColumnName As String) As Object
Dim filterExpression As String = ""
For i = 0 To columnNamesAndKeys.Count
Dim lookupColumn As String = columnNamesAndKeys(i).Item1
Dim lookupKey As Object = columnNamesAndKeys(i).Item2
Dim keyType = lookupKey.GetType()
If keyType IsNot table.Columns(lookupColumn).DataType Then Return Nothing
If keyType Is GetType(String) Then
filterExpression += IIf(Len(filterExpression) > 0, " AND ", "") + $"{lookupColumn} = '{lookupKey}'"
ElseIf keyType Is GetType(Date) Then
filterExpression += IIf(Len(filterExpression) > 0, " AND ", "") + $"{lookupColumn} = #{lookupKey:M/dd/yyyy h:mm:ss tt}#"
Else
filterExpression += IIf(Len(filterExpression) > 0, " AND ", "") + $"{lookupColumn} = {lookupKey}"
End If
Next
Dim row = table.Select(filterExpression).FirstOrDefault()
Return If(row Is Nothing, Nothing, row(resultColumnName))
End Function
Called thus (for a single pair) :
Dim someKey As Integer
Dim someValue = TableLookup(
dtbSomeTable,
New List(Of Tuple(Of String, Object))
From {("SomeKey", DirectCast(someKey, Object)).ToTuple},
"SomeOtherColumn")
And thus (for multiple pairs) :
Dim someKey As Integer
Dim someOtherKey As String
Dim someValue = TableLookup(
dtbSomeTable,
New List(Of Tuple(Of String, Object))
From {("SomeKey", DirectCast(someKey, Object)).ToTuple,
("SomeOtherKey", DirectCast(someOtherKey, Object)).ToTuple},
"SomeOtherColumn")
So - this works - but it feels awfully clunky calling it each time, having to create an ad-hoc list of Tuples, then declare each Tuple and DirectCast each key as an Object to obey the strongly-typed requirement.
The whole point of the function was to provide an easy one-liner throughout the code to quickly look up columns with potentially multiple, arbitrary, criteria but all of these manipulations within the call makes it less intelligible to anyone unfortunate enough to ever have to maintain this...
Is there a smarter / cleaner way to pass an arbitrary set of paired parameters, where one of the pair items needs to be an arbitrary Type?
You can achieve this by using a Parameter Array (ParamArray ) in conjunction with value tuples. This allows you to call the method easily with any number of parameters and no explicit list or array instantiation and neither a New keyword for the values nor any casts. Note that we must swap columnNamesAndKeys and resultColumnName, since the ParamArray used to pass columnNamesAndKeys must be the last parameter of the method.
Usage example:
Dim result = TableLookup(
dtbSomeTable,
"ResultColumn",
("id", 12), ("name", "Joe"), ("date", #2022/10/28#), ("someKey", someValue))
The adapted function:
Private Function TableLookup(
table As DataTable,
resultColumnName As String,
ParamArray columnNamesAndKeys() As (key As String, value As Object)
) As Object
Dim filterExpression As String = ""
For i = 0 To columnNamesAndKeys.Length - 1
Dim lookupColumn As String = columnNamesAndKeys(i).key
Dim lookupKey As Object = columnNamesAndKeys(i).value
Dim keyType = lookupKey.GetType()
If keyType IsNot table.Columns(lookupColumn).DataType Then Return Nothing
If filterExpression.Length > 0 Then
filterExpression += " AND "
End If
If keyType Is GetType(String) Then
filterExpression += $"{lookupColumn} = '{DirectCast(lookupKey, String).Replace("'", "''")}'"
ElseIf keyType Is GetType(Date) Then
filterExpression += $"{lookupColumn} = #{lookupKey:yyyy/MM/dd h:mm:ss tt}#"
Else
filterExpression += $"{lookupColumn} = {lookupKey}"
End If
Next
Dim row = table.Select(filterExpression).FirstOrDefault()
Return If(row Is Nothing, Nothing, row(resultColumnName))
End Function
I also did some refactorings.
Since I used a named tuple, you can access the fields by name instead of just Item1 or `Item2'.
Conditionally adding the " AND " part can be done once before the lengthy If Then Else ...
I replace any single quotes within a string value by two single quotes. E.g., a string like "John's Pub" becomes 'John''s Pub' in SQL notation.
For i = 0 To columnNamesAndKeys.Count is wrong. The index goes from 0 to Count - 1. And since we have an array now, we must use Length.
The below code I tried to sum up the string value with the list values, it happens, but other values are not shown in return. I need to sum the values and other value should be returned to the object using linq in vb.net.
My code:
Dim lstrTaxValue As String = "YQ$40"
Dim lstaValues As New List(Of String)
lstaValues.Add("YQ$10")
lstaValues.Add("TQ$3")
lstaValues.Add("PQ$8")
lstaValues.Add("YQ$10")
lstaValues.Add("TQ$3")
lstaValues.Add("AQ$5")
Dim lobjTValues = (From lstr In lstaValues
From lval In lstrTaxValue.Split(" ")
Where (lstr.Split("$")(0) = CStr(lval).Split("$")(0))
Select (CStr(lval).Split("$")(0) & "$" & (CDbl(CStr(lval).Split("$")(1)) + CDbl(lstr.Split("$")(1))))).ToList()
What am I doing wrong?
To quote Jon Skeet...
Change some value inside the List<T>
In comments...
Why do you want to use lambda expressions? The foreach code works fine and is simple. LINQ is for querying data, not mutating it. – Jon Skeet
Your objective does not seem to lend itself to Linq.
Private Sub OPCode()
Dim lstrTaxValue As String = "YQ$40"
Dim lstaValues As New List(Of String)
lstaValues.Add("YQ$10")
lstaValues.Add("TQ$3")
lstaValues.Add("PQ$8")
lstaValues.Add("YQ$10")
lstaValues.Add("TQ$3")
lstaValues.Add("AQ$5")
Dim TaxValue = lstrTaxValue.Split("$"c)
For i = 0 To lstaValues.Count - 1
If lstaValues(i).Split("$"c)(0) = TaxValue(0) Then
lstaValues(i) = TaxValue(0) & "$" & CStr(CDbl(lstaValues(i).Split("$"c)(1)) + CDbl(TaxValue(1)))
End If
Next
For Each s In lstaValues
Debug.Print(s)
Next
End Sub
Result:
YQ$50
TQ$3
PQ$8
YQ$50
TQ$3
AQ$5
What I want is to return 2 values from the database with a function and then store the values in variables so I can work with them. This is my code.
Function Buscar_Registro(ByVal xId As Integer) As String
Dim a, b As String
'convertir cadena
Dim Id As Integer
Id = xId
'conexión
Dim Conexion As OleDbConnection = New OleDbConnection
Conexion.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=D:\Visual\2000Phrases\2000 Phrases.accdb"
'cadena SQL
Dim CadenaSQL As String = "SELECT * FROM Data WHERE Id = " & Id
'Adaptador
Dim Adaptador As New OleDbDataAdapter(CadenaSQL, Conexion)
'Data set
Dim Ds As New DataSet
'Llenar el Data set
Conexion.Open()
Adaptador.Fill(Ds)
Conexion.Close()
'Contar registro
If (Ds.Tables(0).Rows.Count = 0) Then
Return False
Else
a = Ds.Tables(0).Rows(0)("Nombre").ToString()
b = Ds.Tables(0).Rows(0)("Apellido").ToString()
Ds.Dispose()
Return a
Return b
Return True
End If
End Function
Private Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click
Randomize()
Dim value As Integer = CInt(Int((20 * Rnd()) + 1))
TextBox3.Text = Buscar_Registro(value)
TextBox4.Text =
End Sub
I dont' know how to do it. The function returns only the value of "a"
Thanks
To return more values you need to change your function "as object"
Function Buscar_Registro(ByVal xId As Integer) As Object
and then you can put your return values into an object this way:
Return{a, b, true}
You'll get your values this way:
Dim mObj as object = Buscar_Registro(yourInteger)
you'll have:
a in mObj(0)
b in mObj(1)
True in mObj(2)
adapt it to your needs
EDIT (message to those that downvoted):
Creating a class an using a specific Object (the one created) to make a Function able to return multiple elements is surely the best choice.
Anyway, if someone doesn't know that it's possible to use the method that I showed in my answer, he is probably not (yet) able to create a class. So I think it's better give an usable (but not perfect) answer instead of a perfect (but unusable for the one who asked) answer.
This is what I think. Anyone can think differently.
Your best option here is to create your own class with the data you need and return that.
Public Class Data
Public Property Nombre As String
Public Property Apellido As String
End Class
And then do:
Function Buscar_Registro(ByVal xId As Integer) As Data
....
If (Ds.Tables(0).Rows.Count = 0) Then
Return Nothing
Else
a = Ds.Tables(0).Rows(0)("Nombre").ToString()
b = Ds.Tables(0).Rows(0)("Apellido").ToString()
Ds.Dispose()
return new Data() With {.Nombre = a, .Apellido = b}
End If
End Function
As of VB 15 you can use ValueTuple
Function Buscar_Registro(ByVal xId As Integer) As (Nombre As String, Apellido As String)
....
If (Ds.Tables(0).Rows.Count = 0) Then
Return (Nothing, Nothing)
Else
a = Ds.Tables(0).Rows(0)("Nombre").ToString()
b = Ds.Tables(0).Rows(0)("Apellido").ToString()
Ds.Dispose()
Return (a, b)
End If
End Function
I'm new to .net but wouldn't "ByRef" be the easiest way?
Function Buscar_Registro(ByVal xId As Integer, ByRef a As String, ByRef b As String)
.. eg: have a stri ng
strResult="controlName1.value * controlName2.value"
.. I need to change it to just controlName1.value * controlName2.value so that i can get the output as double value
Please reply
Thanks
If you're using Windows Forms, there is an indexer property that accepts the name of a sub-control as a string and returns the control if a match is found. See: Control.ControlCollection.Item Property (String).aspx
The straightforward alternative in all UI frameworks is to map Strings to Controls like such:
Function MapStringToControl(ctlName As String) As Control
Select Case ctlName
Case "controlName1"
Return controlName1
Case "controlName2"
Return controlName2
Case Else
Return Nothing
End Function
Of course note that there is no .Value property in Windows Forms--you need to do something like Integer.Parse(ctl.Text).
It depends what type of control it is. For example a textbox has a .Text property. A NumericUpDown control has a .Value property.
All you need to do is to convert the appropriate property to the appropriate type. So for TextBoxes:
Dim result as Double = CDbl(txtFoo.Text) * CDbl(txtBar.Text)
For a NumericUpDown:
Dim result as Double = CDbl(nudFoo.Value) * CDbl(numBar.Value)
Hi guys thanks for your updates.. I wrote my own function by using your concepts and some other code snippets .I am posting the result
Function generate(ByVal alg As String, ByVal intRow As Integer) As String
Dim algSplit As String() = alg.Split(" "c)
For index As Int32 = 0 To algSplit.Length - 1
'algSplit(index) = algSplit(index).Replace("#"c, "Number")
If algSplit(index).Contains("[") Then
Dim i As Integer = algSplit(index).IndexOf("[")
Dim f As String = algSplit(index).Substring(i + 1, algSplit(index).IndexOf("]", i + 1) - i - 1)
Dim grdCell As Infragistics.Win.UltraWinGrid.UltraGridCell = dgExcelEstimate.Rows(intRow).Cells(f)
Dim dblVal As Double = grdCell.Value
algSplit(index) = dblVal
End If
Next
Dim result As String = String.Join("", algSplit)
'Dim dblRes As Double = Convert.ToDouble(result)
Return result
End Function
Thanks again every one.. expecting same in future
I have an update function that updates an sql server db table through a dataset. One of the fields in the table is an integer and accepts null values. So when I am populating the update function I need a way to enter a null in when the function wants an integer.
I tried to do it this way but _intDLocation = "" throws an exception
Dim _dLocation As String = udDefaultLocationTextEdit.Text
Dim _intDLocation As Integer
If _dLocation <> "" Then
_intDLocation = Integer.Parse(udDefaultLocationTextEdit.Text)
Else
'NEED HELP HERE
_intDLocation = ""
End If
Integers cannot be set to Null. You have to make the integer "nullable" by adding a question mark after the word Integer. Now _intDLocation is no longer a normal integer. It is an instance of Nullable(Of Integer).
Dim _dLocation As String = udDefaultLocationTextEdit.Text
Dim _intDLocation As Integer?
If _dLocation <> "" Then
_intDLocation = Integer.Parse(udDefaultLocationTextEdit.Text)
Else
_intDLocation = Nothing
End If
Later on, if you want to check for null you can use this handy, readable syntax:
If _intDLocation.HasValue Then
DoSomething()
End If
In some cases you will need to access the value as an actual integer, not a nullable integer. For those cases, you simply access
_intDLocation.Value
Read all about Nullable here.
Try this:
Dim _dLocation As String = udDefaultLocationTextEdit.Text
Dim _intDLocation As Nullable(Of Integer)
If Not String.IsNullOrEmpty(_dLocation) Then
_intDLocation = Integer.Parse(_dLocation)
End If
My application uses a lot of labels that start out blank (Text property), but need to be incremented as integers, so I made this handy function:
Public Shared Function Nullinator(ByVal CheckVal As String) As Integer
' Receives a string and returns an integer (zero if Null or Empty or original value)
If String.IsNullOrEmpty(CheckVal) Then
Return 0
Else
Return CheckVal
End If
End Function
This is typical example of how it would be used:
Dim Match_Innings As Integer = Nullinator(Me.TotalInnings.Text)
Enjoy!
_intDLocation = Nothing