why 0, not Nothing in VB System.Data.DataTable - vb.net

The codes:
Private m_log_dataTable As System.Data.DataTable = Nothing
Private m_freq As String = Nothing
Private m_r As Single = Nothing
Private m_l As Single = Nothing
Private m_c As Single = Nothing
Private m_rp As Single = Nothing
Private m_rs As Single = Nothing
Private m_z As Single = Nothing
Private m_esr As Single = Nothing
Private m_dcr As Single = Nothing
Private m_q As Single = Nothing
Private m_d As Single = Nothing
...
Private Sub LOG()
Try
m_freq = Nothing
m_r = Nothing
m_l = Nothing
m_c = Nothing
m_rp = Nothing
m_rs = Nothing
m_z = Nothing
m_esr = Nothing
m_dcr = Nothing
m_q = Nothing
m_d = Nothing
m_value = Nothing
m_unit = Nothing
m_log_dataTable.Rows.Add(DateTime.Now, getDUT(), getMode(), m_freq, m_r, m_l, m_c, m_r, m_rs, m_z, m_esr, m_dcr, m_q, m_d)'Line1
m_log_dataTable.Rows.Add(DateTime.Now, getDUT(), getMode(), Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing)'Line2
Catch ex As Exception
MsgBox("Exception when logging:" + ex.Message)
End Try
End Sub
Output:
Why line1 (in the above codes) write 0 to the datatable instead of Nothing?
What should I do?
Thanks

You need to use a Nullable type. Change all of your single types to single? and try it again.
Private m_r As Single? = Nothing
Private m_l As Single? = Nothing
// etc
As Gabor commented, you'll need to access the Value property or one of the other methods that are available to Nullable(Of T).
m_r.Value ' Access the underlying value
m_r.GetValueOrDefault() ' Underlying value or, if none, default for the underlying type
m_r.GetValueOrDefault(3) ' Underlying value or, if none, some default value you decide

Nothing in VB.NET equals to default(T) in C# (instead of null). Value types cannot be null therefore in db they are represented in columns with not null constraint.
You indeed should use Nullable(Of Single) (which is the same as Single?) for your fields.
But of course this is not enough. You should modify the columns in the database so that they will have null constraint instead of not null and the System.Data.DataTable should be configured the same way.

Coz The default value of Single is 0.You can use string column or you can define target datagridview's columns.
DataGridViewCellStyle1.Format = "N"
Column1.DefaultCellStyle = DataGridViewCellStyle1

Related

The better technique in this refactoring case?

I need to do some refactoring (actually it's A LOT, but this small step will be very helpful for the whole process). So, let's say I've got this code snippet:
If xmlDoc.SelectSingleNode("/dang") IsNot Nothing Then
universalNode = xmlDoc.SelectSingleNode("/dang")
Type = "dang"
ElseIf xmlDoc.SelectSingleNode("/nang") IsNot Nothing Then
universalNode = xmlDoc.SelectSingleNode("/nang")
Type = "nang"
ElseIf xmlDoc.SelectSingleNode("/lang") IsNot Nothing Then
universalNode = xmlDoc.SelectSingleNode("/lang")
Type = "lang"
ElseIf xmlDoc.SelectSingleNode("/tang") IsNot Nothing Then
universalNode = xmlDoc.SelectSingleNode("/tang")
Type = "tang"
ElseIf xmlDoc.SelectSingleNode("/xtang") IsNot Nothing Then
universalNode = xmlDoc.SelectSingleNode("/xtang")
Type = "xtang"
End If
It's in the body of a big function and I want to take it out to a separate function. So, what I was wondering is whether it will be better to pass both universalNode and Type by value and just assign to them the values, without the need of returning anything? Is that going to work well or it is risky?
If it was just the Type that I'm working on, i.e., then I would just return it, but it's more than 1 variable that is being changed, and both are local variables for the big function, from which I am taking out this code snippet.
Any other suggestions maybe?
You can pass the variables by reference (not by value) to the function, it's not a bad practice and should work:
Public Sub MyFunc(ByRef node As MyNode, ByRef typ As String)
node = ...
typ = ...
End Sub
Or you can return some complex data holder:
Public Class MyParams
Public node As MyNode
Public typ As String
End Class
Public MyParams MyFunc()
Dim result As New MyParams()
result.node = ...
result.typ = ...
Return result
End Sub

Merge two identical DataTables results in DataRowState.Modified

Am I wrong assuming that if two identical DataTables are merged the state of each row will be preserved?
Take a look at this simple example. It creates two identical tables and merge the updated table with original table. But the returned table in original.GetChanges() is not Nothing as expected. Also, the state of each row in the original table are changed to Modified.
So what am I missing? Do I really have to create my own merge method to achieve this?
Public Sub Test()
Dim original As DataTable = Me.CreateTableWithData()
Dim updated As DataTable = Me.CreateTableWithData()
Dim preserveChanges As Boolean = True
Dim msAction As MissingSchemaAction = MissingSchemaAction.Ignore
original.Merge(updated, preserveChanges, msAction)
Dim changes As DataTable = original.GetChanges()
MessageBox.Show(String.Format("Count={0}", If((changes Is Nothing), 0, changes.Rows.Count)), Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information)
If (Not changes Is Nothing) Then changes.Dispose() : changes = Nothing
updated.Dispose() : updated = Nothing
original.Dispose() : original = Nothing
End Sub
Private Function CreateTableWithData() As DataTable
Dim table As New DataTable("TEST")
table.Columns.Add("ID", GetType(Integer))
table.Columns.Add("VALUE", GetType(String))
table.PrimaryKey = New DataColumn() {table.Columns(0)}
table.Rows.Add(1, "Value 1")
table.Rows.Add(2, "Value 2")
table.AcceptChanges()
Return table
End Function
Output: Count=2
Edit - The workaround
The following code is a workaround for this strange(?) behavior.
Private Shared Sub Merge(target As DataTable, source As DataTable, preserveChanges As Boolean, msa As MissingSchemaAction)
target.Merge(source, preserveChanges, msa)
Dim row As DataRow
Dim column As DataColumn
Dim acceptChanges As Boolean
For Each row In target.Rows
If ((row.RowState = DataRowState.Modified) AndAlso ((row.HasVersion(DataRowVersion.Original)) AndAlso (row.HasVersion(DataRowVersion.Default)))) Then
acceptChanges = True
For Each column In target.Columns
If (Not Object.Equals(row.Item(column, DataRowVersion.Original), row.Item(column, DataRowVersion.Default))) Then
acceptChanges = False
Exit For
End If
Next
If (acceptChanges) Then
row.AcceptChanges()
End If
End If
Next
acceptChanges = Nothing
column = Nothing
row = Nothing
End Sub
After some time of working with DataTable merge I found the best solution to merging data, preserving changes and not setting the RowState to Modified for all of the existing rows.
What I discovered is that all of the rows in the original DataTable would have their RowState set to Modified if you use the DataTable Merge and pass True as the preserve changes property. If you pass false instead, the RowStates remain the same.
Going back to the documentation for the DataTable.Merge(DataTable, Boolean, MissingSchemaAction) Method I found this:
...In this scenario, the GetChanges method is first invoked. That method returns a second DataTable optimized for validating and merging. This second DataTable object contains only the DataTable and DataRow objects objects that were changed, resulting in a subset of the original DataTable...
From there I started to realize that the this merge is not really intended to be used with the original data directly... instead you should merge against the table returned by the GetChanges method (passing true in preserving changes) and then merge the changes table into the original source passing false for the preserving changes parameter.
To demonstrate this I have created the following class:
Class TableManger
Implements ComponentModel.INotifyPropertyChanged
Private _table1 As System.Data.DataTable
Private _table2 As System.Data.DataTable
Private _changesDetected As Integer = 0
Public ReadOnly Property Table1
Get
Return _table1
End Get
End Property
Public ReadOnly Property ChangesDetected As Integer
Get
Return _changesDetected
End Get
End Property
Public Sub New()
_table1 = CreateTableWithData()
_table1.AcceptChanges()
AddHandler _table1.RowChanged, New System.Data.DataRowChangeEventHandler(AddressOf Row_Changed)
End Sub
Public Sub MergeTables()
_table2 = _table1.Clone
Dim tableRows As New List(Of System.Data.DataRow)
For Each r In _table1.Rows
Dim dr2 = _table2.NewRow
For Each col As System.Data.DataColumn In _table1.Columns
dr2(col.ColumnName) = r(col.ColumnName)
Next
_table2.Rows.Add(dr2)
tableRows.Add(dr2)
Next
_table2.AcceptChanges()
If _table2.Rows.Count > 0 Then
_table2.Rows(0)(1) = "TB2 Changed"
End If
If _table1.Rows.Count > 0 Then
'_table1.Rows(0)(1) = "TB1 Change"'
_table1.Rows(1)(1) = "TB1 Change"
End If
_changesDetected = 0
Dim perserveChanges As Boolean = True
Dim msAction As System.Data.MissingSchemaAction = System.Data.MissingSchemaAction.Ignore
Dim changes As System.Data.DataTable = _table1.GetChanges()
If changes IsNot Nothing Then
changes.Merge(_table2, perserveChanges, msAction)
_table1.Merge(changes, False, msAction)
Else
_table1.Merge(_table2, False, msAction)
End If
MessageBox.Show(String.Format("Changes in Change Table: {0} {1}Changes Detected: {2}", If((changes Is Nothing), 0, changes.Rows.Count), System.Environment.NewLine, _changesDetected), "Testing")
RaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("Table1"))
RaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("ChangesDetected"))
End Sub
Private Sub Row_Changed(ByVal sender As Object, ByVal e As System.Data.DataRowChangeEventArgs)
Select Case e.Action
Case System.Data.DataRowAction.Change
If e.Row.RowState <> System.Data.DataRowState.Unchanged Then
_changesDetected += 1
End If
End Select
End Sub
Private Function CreateTableWithData() As System.Data.DataTable
Dim newTable As New System.Data.DataTable
Dim columnID As New System.Data.DataColumn("ID", GetType(Guid))
Dim columnA As New System.Data.DataColumn("ColumnA", GetType(String))
Dim columnB As New System.Data.DataColumn("ColumnB", GetType(String))
newTable.Columns.AddRange({columnID, columnA, columnB})
newTable.PrimaryKey = {newTable.Columns(0)}
For i = 0 To 5
Dim dr = newTable.NewRow
dr("ID") = Guid.NewGuid
dr("ColumnA") = String.Format("Column A Row {0}", i.ToString)
dr("ColumnB") = String.Format("Column B Row {0}", i.ToString)
newTable.Rows.Add(dr)
Next
Return newTable
End Function
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
So, in the MergeTables method, I make a change to the first row in _table2 and I make a change in the second row of _table1.
Because I made a change the first row in _table1, the _table1.GetChanges method returns a DataTable with all of the changed rows (just the first row in this case).
I then merge the table containing the changes with _table2 and indicate that I want to preserve changes.
Once that merge is completed I know that the results are preserving the changes that I had made before the merge and that the table will contain the new data as well (so long as there were no conflicts). The result of merging the incoming data into the changes table will resolution of any conflicts in data.
After I have that resolved table I can safely merge into the original _table1 table indicating that preserve change = false. Because passing false as the preserve changes parameter results in no RowState changes for the original data everything works perfectly fine! My changes are preserved And the RowStates are not modified!
Happy Coding!
-Frinny

GetType.GetProperties

I am trying to run through all the controls in a panel and find which properties the user has changed for each control.
So I have this code:
Private Sub WriteProperties(ByVal cntrl As Control)
Try
Dim oType As Type = cntrl.GetType
'Create a new control the same type as cntrl to use it as the default control
Dim newCnt As New Control
newCnt = Activator.CreateInstance(oType)
For Each prop As PropertyInfo In newCnt.GetType().GetProperties
Dim val = cntrl.GetType().GetProperty(prop.Name).GetValue(cntrl, Nothing)
Dim defVal = newCnt.GetType().GetProperty(prop.Name).GetValue(newCnt, Nothing)
If val.Equals(defVal) = False Then
'So if something is different....
End If
Next
Catch ex As Exception
MsgBox("WriteProperties : " & ex.Message)
End Try
End Sub
Now I face three problems:
When the property refers to image (BackGround Image) I have an error :
ImageObject reference not set to an instance of an object.
The second problem is that the code:
If val.Equals(defVal) = False Then
'So if something is different....
End If
is executes sometimes when the val and defVal are the same.
This is happening in cases that the property is a "parentProperty" like FlatAppearance (which has more child properties)
My loop doesn't look into basic properties like Size, or Location which I want
Re: Not set to an instance of an object, do something like ...
If val IsNot Nothing AndAlso defVal IsNot Nothing AndAlso Not val.Equals(defVal) Then
Which will only do the comparison if neither value is Nothing (aka Null).
Unfortunately, #2 is a fundamental problem - .Equals by default checks if the 2 object references point at the same object in memory - eg if You did
Dim A As New SomeClass
Dim B As New SomeClass
If A.Equals(B) Then
...
End If
Would return False unless SomeClass has an overridden equality comparer, which many classes do not.
You could check if the value in question is a type you know you can compare (Integer, String, Double, etc). If not, you could iterate through its properties and perform the same check again. This would allow you to compare the public properties of any type for equality but wouldn't guarantee the internal state of the classes is the same.
Something Like (Untested/Pseudo)...
Function Compare (PropA, PropB) As Boolean
Dim Match = True
If PropA.Value Is Nothing Or PropB.Value Is Nothing
Match = False
Else
If PropA.Value.GetType.IsAssignableFrom(GetType(String)) Or
PropA.Value.GetType.IsAssignableFrom(GetType(Integer)) Or ... Then
Match = PropB.Value.Equals(PropB.Value)
Else
For Each Prop In PropA.Value.GetType.GetProperties()
Match = Compare(Prop, PropB.Value.GetType.GetProperty(Prop.Name))
If Not Match Then Exit For
Next
End If
End If
Return Match
End Function
This is still not ideal as the internal states of the values may differ.

Set Nullable property default value to Nothing not working as desired

I have a property which type is Nullable of Integer an default value Nothing as shown below:
Property TestId As Integer? = Nothing
the following code evaluates the property TestId to Nothing (as wanted)
Dim test As RadTreeNode = rtvDefinitionCreate.FindNodeByValue(DefinitionHeaderEnum.Test)
If test Is Nothing Then
definition.TestId = Nothing
Else
definition.TestId = test.Nodes(0).Value
End If
but the code below evaluates as 0 (default value for Integer, even when is Integer? with default value Nothing)
Dim test As RadTreeNode = rtvDefinitionCreate.FindNodeByValue(DefinitionHeaderEnum.Test)
definition.TestId = If(IsNothing(test), Nothing, test.Nodes(0).Value)
What is wrong with the above code? Any Help??
(later in the code when call the property, the property has 0)
It' because you are compiling you code with Option Strict Off.
If you would compile your code with Option Strict On, the compiler would give you an error, telling you that it can't convert from String to Integer?, avoiding such suprises at runtime.
This is a weirdness when using properties/ternary operator/option strict off in VB.NET.
Consider the following code:
Class Test
Property NullableProperty As Integer? = Nothing
Public NullableField As Integer? = Nothing
End Class
Sub Main()
' Setting the Property directly will lest the ternary operator evaluate to zero
Dim b = New Test() With {.NullableProperty = If(True, Nothing, "123")}
b.NullableProperty = If(True, Nothing, "123")
' Setting the Property with reflection or setting a local variable
' or a public field lets the ternary operator evaluate to Nothing
Dim localNullable As Integer? = If(True, Nothing, "123")
Dim implicitLocal = If(True, Nothing, "123")
b.NullableField = If(True, Nothing, "123")
b.GetType().GetMethod("set_NullableProperty").Invoke(b, New Object() {If(True, Nothing, "123")})
b.GetType().GetProperty("NullableProperty").SetValue(b, If(True, Nothing, "123"), Nothing)
End Sub
Another difference to consider:
Dim localNullable As Integer? = If(True, Nothing, "123")
will evaluate to Nothing but
Dim localNullable As Integer? = If(SomeNonConstantCondition, Nothing, "123")
will evaluate to 0
You can create an extension method to do the nasty work for you.
<Extension()>
Function TakeAs(Of T, R)(obj As T, selector As Func(Of T, R)) As R
If obj Is Nothing Then
Return Nothing
End If
Return selector(obj)
End Function
and call it like
definition.TestId = test.TakeAs(Of Int32?)(Function(o) o.Nodes(0).Value)

Preparing for "nothing" in VB.NET method

I have this method:
Private Sub SetIfNotNull(ByVal input As Object, ByRef destination As Object, ByVal ConversionType As ConversionType)
If input IsNot Nothing AndAlso input <> "" Then
Select Case ConversionType
Case DealerTrackConnection.ConversionType._String
destination = input
Case DealerTrackConnection.ConversionType._Integer
destination = Convert.ToInt32(input)
Case DealerTrackConnection.ConversionType._Double
destination = Convert.ToDouble(input)
Case DealerTrackConnection.ConversionType._Date
destination = Convert.ToDateTime(input)
Case DealerTrackConnection.ConversionType._Decimal
destination = Convert.ToDecimal(input)
End Select
End If
End Sub
And here is one call in which it fails:
SetIfNotNull(ApplicantElement.Element("suffix").Value, NewApplicant.Suffix, ConversionType._String)
If the element from the XML file is nothing (there is no tag), the method call fails. but I am checking for nothing. Why is it doing this and how would I modify the code to fix it this time and everytime.
The problem isn't in your SetIfNotNull method, rather it is in this piece of code: ApplicantElement.Element("suffix").Value
The element is null, so the Value call throws a NullReferenceException. Try this instead:
CType(ApplicantElement.Element("suffix"), String)
Also, you can consolidate the checks in this line:
If input IsNot Nothing AndAlso input <> "" Then
into this:
If Not String.IsNullOrEmpty(input) Then
It seems that ApplicantElement.Element("suffix") is nothing and therefor the exception occurs before your method is called, isn't it?
If Not ApplicantElement.Element("suffix") Is Nothing Then
SetIfNotNull(ApplicantElement.Element("suffix").Value, NewApplicant.Suffix, ConversionType._String)
End If