I have been working on a project which involves sending out emails to our customers in the past week and came across a very interesting error with a DataView object.
The application is required to send out batch emails once a day, so I created an email message queue table to store the outgoing email messages. After a message is sent, the column that stores the delivery status will be changed from 0 to1 so that the next time the application will skip the record. Simple enough. Here are the steps:
Loads the records in the email message queue table within a given date range into a DataTable object dt.
Creates a DataView object out of the DataTable with RowsFilter=”Sent=0″:
Dim dv As DataView = New DataView(dt)dv.RowsFilter = "Sent=0"
Loops through the DataView object, sends out emails, update the delivery status to Sent=1.
For i As Integer = 0 To dv.Count - 1 ' Send out the email ' Update the record dv.Item(i)("Sent") = True ' Update the database Next
Looks very logical, right? But I got a System.ArgumentOutOfRangeException error on the loop. After a couple hours of investigation, I finally found the cause of the exception.
It turned out that the exception error only happens when I update the value of the column on which I set my filter, but no error is encountered if I change the values of other columns:
dv.Item(i)("SentDate") = DateTime.Now ' No error dv.Item(i)("SentBy") = username ' No error dv.Item(i)("Sent") = True ' Error!!
When stepping through the code, an interesting thing shows up. After the execution of the line
dv.Item(i)("Sent") = True
the total number of records in the DataView object is decreased by 1 automatically. It seems that the data in the DataView object does not remain static, and some underlying event handler is called to re-apply the filter criteria and to return a new total count. Therefore, when the loop reaches the end of the DataView object, the index i is already greater than the total count and the ArgumentOutOfRangeException is thrown.
I am not sure if it is designed this way, or it is related to this known bug: http://support.microsoft.com/default.aspx?scid=kb;en-us;836874
Workaround:
Replace the For loop with a While loop:
While dvEmails.Count <> 0 Dim i As Integer = 0 ' Send email' Update the record
dvEmails.Item(i)("SentDate") = DateTime.Now.ToString
dvEmails.Item(i)("IsSent") = TrueemailQueue.Save()End While