 |
|
 |
With overload "magic", you can simply write :
Public Shared Function NVL(ByVal value As DBNull, ByVal emptyValue As Object) As Object
Return emptyValue
End Function
Public Shared Function NVL(ByVal value As DBNull, ByVal emptyValue As ValueType) As ValueType
Return emptyValue
End Function
Public Shared Function NVL(ByVal value As Object, ByVal emptyValue As Object) As Object
Return value
End Function
Public Shared Function NVL(ByVal value As ValueType, ByVal emptyValue As ValueType) As ValueType
Return value
End Function
#If DEBUG Then
Private Sub TestNVL()
Debug.Write("NVL values : ")
Debug.Write(NVL(DBNull.Value, "test1"))
Debug.Write(", ")
Debug.Write(NVL(DBNull.Value, 1))
Debug.Write(", ")
Debug.Write(NVL(Nothing, "test2"))
Debug.Write(", ")
Debug.Write(NVL("val", "test3"))
Debug.Write(", ")
Debug.Write(NVL(1234, 3))
Debug.Write(", ")
Debug.WriteLine(NVL(New Decimal(123.345), 4))
Debug.WriteLine("Correct values : test1, 1, test2, val, 1234, 123.345")
End Sub
#End If
If you wonder where NVL comes from, it's how that function is named in Oracle.
Sébastien
Intelligence shared is intelligence squared.
Homepage : http://sebastienlorion.com
|
|
|
|
 |
|
|
 |
|
 |
I ran through your code and it works as expected. This is a clever solution to this issue. However, there are a couple of tasks the NVL functions don't appear to accomplish:
1. They don't cast values. For instance, if I pass dataTable.Rows(0).Item("FirstName") to your NVL functions I get back an Object (not a String). If I pass this same value to NullSafeString I receive a String, pass it to NullSafeInteger I receive an integer, etc... You can solve #1 by adding an appropriate CType call around each call to NVL, but one of the beauties of the NullSafe functions is they eliminate the need for these calls.
2. If you do make the calls to CType, then let's say the value in dt.Rows(0).Item("ShouldBeBoolean") is "Tru"; an exception will be thrown when you pass this to NVL and use CBool (see code below).
If you pass the same value to NullSafeBoolean you receive the correct return value. The same goes with invalid dates, invalid integers, etc... Of course, you could add Try/Catch blocks around all of these casts, but then you're almost back to the first block of code in the article.
Please post again if you have any ideas, I am interested in finding a way around these issues!
#If DEBUG Then
Private Sub TestNVL()
Dim dt As New DataTable("Test")
dt.Columns.Add("ShouldBeBoolean")
dt.Rows.Add(New Object() {"Tru"})
Debug.Write("NVL value : ")
Debug.Write(CBool(NVL(dt.Rows(0).Item("ShouldBeBoolean"), True)))
Debug.WriteLine("Correct value : False")
End Sub
#End If
|
|
|
|
 |
|
 |
1.
To call the value type version of NVL, the internal type of the value argument must be a value type (ie the boxed type) AND the type of the emptyValue argument must also be a value type.
Dim o As Object
o = 1
NVL(o, 1)
o = "asdf"
NVL(o, 1)
NVL(DBNull.Value, 1) NVL(DBNull.Value, "asdf")
If you want a strongly typed value, simply wrap the call to NVL with the casting as DirectCast(NVL(...), GetType(String)) or CType(NVL(...), GetType(Integer)). Please note that DirectCast is much faster than CType and that CBool, CInt, etc. are from the VisualBasic namespace which you might want to avoid.
2.
As for your example, I would expect this code to fail and not return False. If you have "Tru" instead of "True" in your DB, that clearly shows some logic error somewhere and I think it is really a bad practice to let it go like you suggest. If you really want to do it that way, then one straighforward way :
Public Shared Function NVL(ByVal value As Object, ByVal emptyValue As Boolean) As Boolean
Try
Return Convert.ToBoolean(value)
Catch ex As InvalidCastException
Return emptyValue
Catch ex As System.FormatException
Return emptyValue
End Try
End Function
'repeat for each value type and any other type you want to manually handle like String
'Generic version
Public Shared Function NVL(ByVal value As Object, ByVal emptyValue As Object) As Object
Try
Return Convert.ChangeType(value, emptyValue.GetType())
Catch ex As System.InvalidCastException
Return emptyValue
Catch ex As System.FormatException
Return emptyValue
End Try
End Function
There might be a way of doing this without raising exceptions which are very expensive, probably using type converters (System.ComponentModel.XXXTypeConverter), but I leave that as an exercise for the reader
Sébastien
Intelligence shared is intelligence squared.
Homepage : http://sebastienlorion.com
|
|
|
|
 |
|
 |
While I agree with your approach, I prefer to use the NullSafe functions I've presented for a couple of reasons:
1. They eliminate somewhat verbose DirectCast calls throughout my code, which make it less readable.
2. Your approach doesn't allow code the option to not submit the emptyValue argument. Since my methods are strongly typed I can take advantage of optional parameters to provide a default emptyValue argument, thereby slimming the calling code even more.
Since these functions are called in literally hundreds of places in a medium-sized application, and possibly thousands of places in a large application, my goal is to minimize complexity of the calling code for better readability and maintainability.
In the end it's up to each developer to make his or her choice. I certainly appreciate Sébastien's feedback as it provides a more complete picture of the options.
Rob
|
|
|
|
 |
|
 |
Useful...But they should do MUCH more error checking before resorting to dealing with exceptions....
|
|
|
|
 |
|
|
 |
|
 |
Sadly point 1 isn't correct:
Sébastien Lorion wrote: 1.
To call the value type version of NVL, the internal type of the value argument must be a value type (ie the boxed type) AND the type of the emptyValue argument must also be a value type.
Dim o As Object
o = 1
NVL(o, 1) 'Call to value type version
o = "asdf"
NVL(o, 1) 'Call to object version
NVL(DBNull.Value, 1) 'Call to value type version
NVL(DBNull.Value, "asdf") 'Call to object value
The overload resolution is done at compile type and uses the type as declared, not the type of the object that is currently assigned to that variable.
You can see this if you change the test function to:
Private Sub TestNVL()
Dim o As Object = DBNull.Value
Debug.Write("NVL values : ")
Debug.Write(NVL(o, "test1"))
Debug.Write(", ")
Debug.Write(NVL(o, 1))
Debug.Write(", ")
o = Nothing
Debug.Write(NVL(o, "test2"))
Debug.Write(", ")
o = "val"
Debug.Write(NVL(o, "test3"))
Debug.Write(", ")
o = 1234
Debug.Write(NVL(o, 3))
Debug.Write(", ")
o = New Decimal(123.345)
Debug.WriteLine(NVL(o, 4))
Debug.WriteLine("Correct values : test1, 1, test2, val, 1234, 123.345")
End Sub
Which produces this instead of the expected values:
NVL values : , , , val, 1234, 123.345
More importantly if you attempt to cast the result instead of calling .ToString on it (which Debug.WriteLine does) an exception will be raised as the following proves:
Private Sub TestNVL()
Dim o As Object = DBNull.Value
Debug.Write("NVL values : ")
Dim s As String = DirectCast(NVL(o, "test"), String)
Debug.Write(NVL(s, "test"))
Debug.WriteLine("Correct value : test")
End Sub
So the NVL function will only work as expected if the input values have already been cast to the correct type - which defeats its purpose!
|
|
|
|
 |
|
 |
Simpler still!!!
If you have created a Typed Dataset, edit the default property of each field.
Typically, I set strings to String.Empty(), or a value like "N/A",
numbers can be 0 (or anything else)
the code is automatically included in the Typed Dataset code. For Example:
Public Property I_CITY As String
Get
If Me.IsI_CITYNull Then
Return String.Empty
Else
Return CType(Me(Me.tableMembers.I_CITYColumn),String)
End If
End Get
Set
Me(Me.tableMembers.I_CITYColumn) = value
End Set
End Property
|
|
|
|
 |
|
 |
If I am not mistaken, the point of the article was to provide a generic way of handling this part of your example :
If Me.IsI_CITYNull Then
Return String.Empty
Else
Return CType(Me(Me.tableMembers.I_CITYColumn),String)
End If
Otherwise, you will have to repeat that kind of code everytime it is needed instead of centralizing its use and behavior.
Sébastien
Intelligence shared is intelligence squared.
Homepage : http://sebastienlorion.com
|
|
|
|
 |
|
 |
No,
The point I was trying to make is that you don't have to code anything.
By setting the NullValue property for the elements of the XSD Schema, in the schema editor,
the code to substitute the value is automatically generated in the Typed Dataset by the code generator built into Visual Studio (or the XSD.exe tool if you don't have VS).
VS has many features that allow you to create complex data access components without writing ANY code.
Hint: start off with a COMPONENT instead of a CLASS. Then you can drag and drop DataAdapters and use the Wizards to configure the SQL, mappings, and Typed Dataset creation.
Matthew
|
|
|
|
 |
|
 |
... but then you discover that typed datasets are such big fat monsters that it's better staying away from them.
Also, one-way generated code will always have the drawback that you cannot modify it because your changes will be lost the next time you regenerate the code.
Sébastien
Intelligence shared is intelligence squared.
Homepage : http://sebastienlorion.com
|
|
|
|
 |
|
 |
If you need alternate functionality, Inherit or wrap the Typed Dataset in another class. That way you separate the changed code from the generated code.
Antoher approach is to use something like CodeSmith (www.ericjsmith.com/codesmith) to create typed datasets of your design.
Remeber, every line of code you don't have to write, is one you didn't make a mistake on. Even better, you still get paid for it.
I typically will change the XSD file many times during development (I use Test Driven methodologies) and just keep using the regenerated Dataset.
create/modify a test
Add new tables, new fields, change types, modify SQL,
generate the Dataset
fix the code to use the new/renamed/deleted properties and methods
run the test
Repeat as required
You get to work on the business value, not the infrastructure.
Matthew
|
|
|
|
 |
|
 |
CodeSmith is quite useful, but even a barebone dataset is a PITA. Just try to serialize it in something else than XML in a generic way. After, try to do intelligent caching, like having certain DataTable static while others are loaded everytime, while maintaining relations amongst those tables.
Everywhere you look including MSDN, there is one constant in what is said : DataSet are heavyweigth objects which should be avoided if performance is an issue.
Finally, business value is directly related to good design, ie a good infrastructure.
Sébastien
Intelligence shared is intelligence squared.
Homepage : http://sebastienlorion.com
|
|
|
|
 |
|