Link to home
Start Free TrialLog in
Avatar of mehmast
mehmast

asked on

vb.net parsing the CSV file - Collection was modified; enumeration operation may not execute

Hello Experts
I have a situation and wonder if someone can advice how to handle this.

In the example below I have 2 orders to process in the CSV file, I will loop through the total items using  aCSV.OrderRequest.Items
and the first thing I do is to check if an order already exist, if it does then I reject the order and reduce the total number of items by 1, so the new values of

aCSV.OrderRequest.Items = 1, however the code intended to loop through for 2 times now when I have removed 1 item, its failing when it comes "Next" statement with an exception below

"Collection was modified; enumeration operation may not execute."

 
   Try
            For Each aCSV As xCSV In _CSVs
                For Each Item As cCSVProductItem In aCSV.OrderRequest.Items

                    NumOrders = Orders.IsRetailerOrderIdExist(Item.OrderItemNumber)
                    If (NumOrders.HasValue AndAlso NumOrders.Value > 0) Then
                        aCSV.RetailerOrderID = NumOrders.Value

                        'If the order id already exist - SEND THE CANCELLATION DISPATCH NOTFICATION
                        CreateCancelOrderNotification(GetOrderStatusFileName("REJECTED"), aCSV, Item)

                       
                        aCSV.OrderRequest.RemoveItem(Item.OrderItemNumber)
                    Else
                        'To make sure the product is on the list,
                        If CheckIfProductExist(aCSV, Item) Then
                            'Capture the order and save in the database.
                            UpdateGiftDB(aCSV, Item)
                        End If

                    End If
                Next
            Next
        Catch ex As Exception
             Return False
        End Try

Open in new window

Avatar of it_saige
it_saige
Flag of United States of America image

You cannot remove items from the list while you are enumerating over it in this way.  In order to remove items while you enumerate the list, you have to enumerate the list in reverse.

Either:
Imports System.Linq

Module Module1
	Sub Main()
		Dim Items As New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
		For Each item As Integer In Items.AsEnumerable().Reverse()
			If item Mod 2 = 0 Then
				Items.Remove(item)
			End If
		Next
		Console.WriteLine("Using Enumerable.Reverse():")
		For Each item As Integer In Items
			Console.WriteLine(item)
		Next
		Console.WriteLine()
		Items = New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
		For i As Integer = Items.Count - 1 To 0 Step -1
			If Items(i) Mod 2 = 0 Then
				Items.RemoveAt(i)
			End If
		Next
		Console.WriteLine("Standard For Loop with Index:")
		For Each item As Integer In Items
			Console.WriteLine(item)
		Next
		Console.ReadLine()
	End Sub
End Module

Open in new window


Produces the following output:User generated image
-saige-
Avatar of mehmast
mehmast

ASKER

Hello Saige
Thank you so much for picking this up for me and for the example,
I am unable to do this though ?

  For Each Item As cCSVProductItem In aCSV.OrderRequest.Items.AsEnumerable().Reverse()
  .....
  Next

How can I adapt it in my code please?
What compiler error do you receive?

-saige-
An alternative approach would be to make a copy of your class, iterate through the copy and remove items from the original. You can then delete the copy.

You do this using the memberwiseclone property

copy=original.memberwiseclone
Avatar of mehmast

ASKER

Hi,
I cannot find AsEnumerable method in the list, and when I just used "Reverse" I get the following exception

Expression does not produce a value
Avatar of mehmast

ASKER

Hello ChloesDad
Thanks for the suggestion. Sorry I did not understand what you meant by copying the class?
Please can you advice, below is my class
myclass.vb
---------------
Imports System
Imports System.Collections.Generic

Public Structure cCSVProductItem

    Private _OrderItemNumber As String
    Public Property OrderItemNumber() As String
        Get
            Return _OrderItemNumber
        End Get
        Set(ByVal value As String)
            _OrderItemNumber = value
        End Set
    End Property

   
    Private _Quantity As Double
    Public Property Quantity() As Double
        Get
            Return _Quantity
        End Get
        Set(ByVal value As Double)
            _Quantity = value
        End Set
    End Property 
End Structure
 
Public Structure cCSVOrderRequest

    Private _Items As List(Of cCSVProductItem)
    Public Property Items() As List(Of cCSVProductItem)
        Get
            Return _Items
        End Get
        Set(ByVal value As List(Of cCSVProductItem))
            _Items = value
        End Set
    End Property


    Sub RemoveItem(ByVal Item As cCSVProductItem)
        Me._Items.Remove(Item)
    End Sub

    Sub RemoveItem(ByVal OrderNumber As String)
        For i As Integer = 0 To Me._Items.Count - 1
            If Me._Items(i).OrderItemNumber = OrderNumber Then
                Dim Item As cCSVProductItem
                Item = Me._Items(i)
                Me.RemoveItem(Item)
                Exit For
            End If
        Next
    End Sub
End Structure
Public Class xCSV

    Private _CSVFileName As String = String.Empty
    Public Property CSVFileName() As String
        Get
            Return _CSVFileName
        End Get
        Set(ByVal value As String)
            _CSVFileName = value
        End Set
    End Property

     
    Private _OrderRequest As cCSVOrderRequest
    Public Property OrderRequest() As cCSVOrderRequest
        Get
            Return _OrderRequest
        End Get
        Set(ByVal value As cCSVOrderRequest)
            _OrderRequest = value
        End Set
    End Property

    
 
    Public Sub New()
        '_OrderRequest.Items = New List(Of cCSVProductItem)
    End Sub
End Class

Open in new window

Ensure that you have Imports System.Linq to the top of your codefile, then use:  

aCSV.OrderRequest.Items.AsEnumerable().Reverse()

-saige-
In your code, make the following changes

Dim copyOfOrder as ....... (whatever the class type acsv.orderrequest  is)

Try
            For Each aCSV As xCSV In _CSVs

                CopyOfOrder = acsv.orderrequest.memberwiseclone

                For Each Item As cCSVProductItem In copyoforder.Items

                    NumOrders = Orders.IsRetailerOrderIdExist(Item.OrderItemNumber)
                    If (NumOrders.HasValue AndAlso NumOrders.Value > 0) Then
                        aCSV.RetailerOrderID = NumOrders.Value

                        'If the order id already exist - SEND THE CANCELLATION DISPATCH NOTFICATION
                        CreateCancelOrderNotification(GetOrderStatusFileName("REJECTED"), aCSV, Item)

                       
                        aCSV.OrderRequest.RemoveItem(Item.OrderItemNumber)
                    Else
                        'To make sure the product is on the list,
                        If CheckIfProductExist(aCSV, Item) Then
                            'Capture the order and save in the database.
                            UpdateGiftDB(aCSV, Item)
                        End If

                    End If
                Next
            Next
        Catch ex As Exception
             Return False
        End Try

Open in new window

Avatar of mehmast

ASKER

Thanks Saige - I am still on 2.0 and not sure I can include linq dll in my project.

Thanks ChloesDad, I have amended the code as suggested below but is still giving me the same error

"Collection was modified; enumeration operation may not execute."

Please advice
 Dim TempOrderRequest As cCSVOrderRequest = New cCSVOrderRequest

        Try
            For Each aCSV As xCSV In _CSVs
                '  Private _CSVs As List(Of xCSV)

                TempOrderRequest = aCSV.OrderRequest
                For Each Item As cCSVProductItem In TempOrderRequest.Items


                    NumOrders = Orders.IsRetailerOrderIdExist(Item.OrderItemNumber)
                    If (NumOrders.HasValue AndAlso NumOrders.Value > 0) Then
                        aCSV.RetailerOrderID = NumOrders.Value

                        'If the order id already exist - SEND THE CANCELLATION DISPATCH NOTFICATION
                        CreateCancelOrderNotification(GetOrderStatusFileName("REJECTED"), aCSV, Item)

                        'Send email to Admin
                        CommonFunctions.SendErrorEmail("DUPLICATE")

                    
                        UploadFilesToFtp(aCSV.CSVFileName)
                        aCSV.OrderRequest.RemoveItem(Item.OrderItemNumber)
                    Else
                       
                        If CheckIfProductExist(aCSV, Item) Then
                            'Capture the order and save in the database.
                            UpdateGiftDB(aCSV, Item)
                        End If

                    End If
                Next
            Next

Open in new window

Which is why I posted the for with index version as well.  That one does not require the System.Linq reference.

-saige-
As a refresher:
Module Module1
	Sub Main()
		Dim Items As New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
		For i As Integer = Items.Count - 1 To 0 Step -1
			If Items(i) Mod 2 = 0 Then
				Items.RemoveAt(i)
			End If
		Next
		Console.WriteLine("Standard For Loop with Index:")
		For Each item As Integer In Items
			Console.WriteLine(item)
		Next
		Console.ReadLine()
	End Sub
End Module

Open in new window


Produces the following output -User generated image
-saige-
You did not copy my code.

My Line
                CopyOfOrder = acsv.orderrequest.memberwiseclone

Your Line

                TempOrderRequest = aCSV.OrderRequest
 
You missed off the MemberwiseClone, so you got another pointer to the same class, rather than a new instance of the class with the same values as the first.
Avatar of mehmast

ASKER

Saige
Thanks again for the help - index method seems to have been working OK, only issue is that its processing last order first and so on ...

ChloesDad
I have tried using your line but then it was throwing the exception below..

Error      2      'Object.Protected Function MemberwiseClone() As Object' is not accessible in this context because it is 'Protected'.       

I would like to make this method works but not sure what I am doing wrong.

Thank you so much both for your help!!! Really appreciate it.
Can you provide a sample xml file?

-saige-
Avatar of mehmast

ASKER

Hi Saige
I am using CSV to process bulk orders, please see attached.

This is my revise code
 For Each aCSV As xCSV In _CSVs
                For i As Integer = aCSV.OrderRequest.Items.Count - 1 To 0 Step -1

                    NumOrders = Orders.IsRetailerOrderIdExist(aCSV.OrderRequest.Items(i).OrderItemNumber)
                    If (NumOrders.HasValue AndAlso NumOrders.Value > 0) Then
                        aCSV.RetailerOrderID = NumOrders.Value

                        'If the order id already exist - SEND THE CANCELLATION DISPATCH NOTFICATION
                        CreateCancelOrderNotification(GetOrderStatusFileName("REJECTED"), aCSV, aCSV.OrderRequest.Items(i))

                        'Send email to Admin
                        CommonFunctions.SendErrorEmail(EmailOrderDetailsWhenException(aCSV, aCSV.OrderRequest.Items(i).OrderItemNumber))

                        'Upload the cancel/reject notification
                        UploadFilesToFtp(aCSV.CSVFileName)
                        ' aCSV.OrderRequest.RemoveItem(aCSV.OrderRequest.Items(i).OrderItemNumber)
                        aCSV.OrderRequest.Items.Remove(aCSV.OrderRequest.Items(i))
                    Else
                        If CheckIfProductExist(aCSV, aCSV.OrderRequest.Items(i)) Then
                            'Capture the order and save in the database.
                            UpdateGiftDB(aCSV, aCSV.OrderRequest.Items(i))
                        Else
                            aCSV.OrderRequest.Items.Remove(aCSV.OrderRequest.Items(i))
                        End If

                    End If
                Next
            Next

Open in new window

TEST.csv
To implement Chloe's recommendation we need to change a few things.  Here are the modified classes that should assist with this:

cCSVOrderRequest -
Public Structure cCSVOrderRequest
	Private _Items As List(Of cCSVProductItem)
	Public Property Items() As List(Of cCSVProductItem)
		Get
			Return _Items
		End Get
		Set(ByVal value As List(Of cCSVProductItem))
			_Items = value
		End Set
	End Property

	Sub RemoveItem(ByVal Item As cCSVProductItem)
		Me._Items.Remove(Item)
	End Sub

	Sub RemoveItem(ByVal OrderNumber As String)
		For i As Integer = 0 To Me._Items.Count - 1
			If Me._Items(i).OrderItemNumber = OrderNumber Then
				Dim Item As cCSVProductItem
				Item = Me._Items(i)
				Me.RemoveItem(Item)
				Exit For
			End If
		Next
	End Sub

	Public Shadows Function MemberwiseClone() As cCSVOrderRequest
		Return Me.MemberwiseClone()
	End Function

	Public Overrides Function Equals(ByVal obj As Object) As Boolean
		If obj Is Nothing OrElse Not obj.GetType().Equals(Me.GetType()) Then
			Return False
		End If
		Return Me = (DirectCast(obj, cCSVOrderRequest))
	End Function

	Public Overloads Function Equals(ByVal obj As cCSVOrderRequest) As Boolean
		If Object.ReferenceEquals(obj, Nothing) Then
			Return False
		End If

		Return Me = obj
	End Function

	Public Shared Operator =(ByVal lhs As cCSVOrderRequest, ByVal rhs As cCSVOrderRequest) As Boolean
		If Object.ReferenceEquals(lhs, rhs) Then
			Return True
		End If

		If Object.ReferenceEquals(lhs, Nothing) OrElse Object.ReferenceEquals(rhs, Nothing) Then
			Return False
		End If

		Return If(Not lhs.Items Is Nothing AndAlso Not rhs.Items Is Nothing, lhs.Items.Equals(rhs.Items), False)
	End Operator

	Public Shared Operator <>(ByVal lhs As cCSVOrderRequest, ByVal rhs As cCSVOrderRequest) As Boolean
		Return Not lhs = rhs
	End Operator
End Structure

Open in new window


xCSV -
Public Class xCSV
	Private _CSVFileName As String = String.Empty
	Public Property CSVFileName() As String
		Get
			Return _CSVFileName
		End Get
		Set(ByVal value As String)
			_CSVFileName = value
		End Set
	End Property

	Private _OrderRequest As cCSVOrderRequest
	Public Property OrderRequest() As cCSVOrderRequest
		Get
			Return _OrderRequest
		End Get
		Set(ByVal value As cCSVOrderRequest)
			_OrderRequest = value
		End Set
	End Property

	Public Sub New()
		'_OrderRequest.Items = New List(Of cCSVProductItem)
	End Sub

	Public Shadows Function MemberwiseClone() As xCSV
		Return Me.MemberwiseClone()
	End Function

	Public Overrides Function Equals(ByVal obj As Object) As Boolean
		If obj Is Nothing OrElse Not obj.GetType().Equals(Me.GetType()) Then
			Return False
		End If
		Return Me = (DirectCast(obj, xCSV))
	End Function

	Public Overloads Function Equals(ByVal obj As xCSV) As Boolean
		If Object.ReferenceEquals(obj, Nothing) Then
			Return False
		End If

		Return Me = obj
	End Function

	Public Shared Operator =(ByVal lhs As xCSV, ByVal rhs As xCSV) As Boolean
		If Object.ReferenceEquals(lhs, rhs) Then
			Return True
		End If

		If Object.ReferenceEquals(lhs, Nothing) OrElse Object.ReferenceEquals(rhs, Nothing) Then
			Return False
		End If

		Return If(Not lhs.CSVFileName Is Nothing AndAlso Not rhs.CSVFileName Is Nothing, lhs.CSVFileName.Equals(rhs.CSVFileName), False) AndAlso If(Not lhs.OrderRequest = Nothing AndAlso Not rhs.OrderRequest = Nothing, lhs.OrderRequest.Equals(rhs.OrderRequest), False)
	End Operator

	Public Shared Operator <>(ByVal lhs As xCSV, ByVal rhs As xCSV) As Boolean
		Return Not lhs = rhs
	End Operator
End Class

Open in new window


Now try this updated loop code to see if this works for you:
Try
	For Each aCSV As xCSV In _CSVs
		'  Private _CSVs As List(Of xCSV)
		TempOrderRequest = aCSV.OrderRequest.MemberwiseClone()
		For Each Item As cCSVProductItem In TempOrderRequest.Items
			NumOrders = Orders.IsRetailerOrderIdExist(Item.OrderItemNumber)
			If (NumOrders.HasValue AndAlso NumOrders.Value > 0) Then
				aCSV.RetailerOrderID = NumOrders.Value

				'If the order id already exist - SEND THE CANCELLATION DISPATCH NOTFICATION
				CreateCancelOrderNotification(GetOrderStatusFileName("REJECTED"), aCSV, Item)

				'Send email to Admin
				CommonFunctions.SendErrorEmail("DUPLICATE")

				UploadFilesToFtp(aCSV.CSVFileName)
				aCSV.OrderRequest.RemoveItem(Item.OrderItemNumber)
			Else
				If CheckIfProductExist(aCSV, Item) Then
					'Capture the order and save in the database.
					UpdateGiftDB(aCSV, Item)
				Else
					aCSV.OrderRequest.RemoveItem(Item.OrderItemNumber)
				End If
			End If
		Next
	Next
Catch ex As Exception
	Console.WriteLine(String.Format("An exception occured: {0}", ex.Message))
End Try

Open in new window


-saige-
Avatar of mehmast

ASKER

thank you again Saige,
I will try this now, when I copy across your code, I am getting "Expression Expected" on this line

            Return If(Not lhs.CSVFileName Is Nothing AndAlso Not rhs.CSVFileName Is Nothing, lhs.CSVFileName.Equals(rhs.CSVFileName), False) AndAlso If(Not lhs.OrderRequest = Nothing AndAlso Not rhs.OrderRequest = Nothing, lhs.OrderRequest.Equals(rhs.OrderRequest), False)


May be I can use nested conditional statement for this?

Thank you
Yes this can be a nested conditional statment:

cCSVOrderRequest -
Public Structure cCSVOrderRequest
	Private _Items As List(Of cCSVProductItem)
	Public Property Items() As List(Of cCSVProductItem)
		Get
			Return _Items
		End Get
		Set(ByVal value As List(Of cCSVProductItem))
			_Items = value
		End Set
	End Property

	Sub RemoveItem(ByVal Item As cCSVProductItem)
		Me._Items.Remove(Item)
	End Sub

	Sub RemoveItem(ByVal OrderNumber As String)
		For i As Integer = 0 To Me._Items.Count - 1
			If Me._Items(i).OrderItemNumber = OrderNumber Then
				Dim Item As cCSVProductItem
				Item = Me._Items(i)
				Me.RemoveItem(Item)
				Exit For
			End If
		Next
	End Sub

	Public Shadows Function MemberwiseClone() As cCSVOrderRequest
		Return Me.MemberwiseClone()
	End Function

	Public Overrides Function Equals(ByVal obj As Object) As Boolean
		If obj Is Nothing OrElse Not obj.GetType().Equals(Me.GetType()) Then
			Return False
		End If
		Return Me = (DirectCast(obj, cCSVOrderRequest))
	End Function

	Public Overloads Function Equals(ByVal obj As cCSVOrderRequest) As Boolean
		If Object.ReferenceEquals(obj, Nothing) Then
			Return False
		End If

		Return Me = obj
	End Function

	Public Shared Operator =(ByVal lhs As cCSVOrderRequest, ByVal rhs As cCSVOrderRequest) As Boolean
		If Object.ReferenceEquals(lhs, rhs) Then
			Return True
		End If

		If Object.ReferenceEquals(lhs, Nothing) OrElse Object.ReferenceEquals(rhs, Nothing) Then
			Return False
		End If

		Dim result As Boolean
		If lhs.Items IsNot Nothing AndAlso rhs.Items IsNot Nothing Then
			result = lhs.Items.Equals(rhs.Items)
		Else
			result = False
		End If
		Return result
	End Operator

	Public Shared Operator <>(ByVal lhs As cCSVOrderRequest, ByVal rhs As cCSVOrderRequest) As Boolean
		Return Not lhs = rhs
	End Operator
End Structure

Open in new window


xCSV -
Public Class xCSV
	Private _CSVFileName As String = String.Empty
	Public Property CSVFileName() As String
		Get
			Return _CSVFileName
		End Get
		Set(ByVal value As String)
			_CSVFileName = value
		End Set
	End Property

	Private _OrderRequest As cCSVOrderRequest
	Public Property OrderRequest() As cCSVOrderRequest
		Get
			Return _OrderRequest
		End Get
		Set(ByVal value As cCSVOrderRequest)
			_OrderRequest = value
		End Set
	End Property

	Public Sub New()
		'_OrderRequest.Items = New List(Of cCSVProductItem)
	End Sub

	Public Shadows Function MemberwiseClone() As xCSV
		Return Me.MemberwiseClone()
	End Function

	Public Overrides Function Equals(ByVal obj As Object) As Boolean
		If obj Is Nothing OrElse Not obj.GetType().Equals(Me.GetType()) Then
			Return False
		End If
		Return Me = (DirectCast(obj, xCSV))
	End Function

	Public Overloads Function Equals(ByVal obj As xCSV) As Boolean
		If Object.ReferenceEquals(obj, Nothing) Then
			Return False
		End If

		Return Me = obj
	End Function

	Public Shared Operator =(ByVal lhs As xCSV, ByVal rhs As xCSV) As Boolean
		If Object.ReferenceEquals(lhs, rhs) Then
			Return True
		End If

		If Object.ReferenceEquals(lhs, Nothing) OrElse Object.ReferenceEquals(rhs, Nothing) Then
			Return False
		End If

		Dim result As Boolean = False
		If Not lhs.CSVFileName Is Nothing AndAlso Not rhs.CSVFileName Is Nothing Then
			If Not lhs.OrderRequest = Nothing AndAlso Not rhs.OrderRequest = Nothing Then
				result = lhs.CSVFileName.Equals(rhs.CSVFileName) AndAlso lhs.OrderRequest.Equals(rhs.OrderRequest)
			Else
				result = False
			End If
		Else
			result = False
		End If
		Return result
	End Operator

	Public Shared Operator <>(ByVal lhs As xCSV, ByVal rhs As xCSV) As Boolean
		Return Not lhs = rhs
	End Operator
End Class

Open in new window


-saige-
Avatar of mehmast

ASKER

Sure thanks again

I am now getting exception :  System.StackOverflowException was unhandled

in the below below

Dim TempOrderRequest As cCSVOrderRequest = New cCSVOrderRequest
>>     TempOrderRequest = aCSV.OrderRequest.MemberwiseClone()

-------------------------------------------------------------------------------------------------
 Public Shadows Function MemberwiseClone() As cCSVOrderRequest
        Return Me.MemberwiseClone()
 End Function
SOLUTION
Avatar of ChloesDad
ChloesDad
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I would suggest that it_saige's final comment http#:a40450236 as the accepted solution as this gives the final code with my comment http#:a40448196 as an assisted solution as that provided the method required to solve the problem. a 50-50 share of points would seem reasonable.
Avatar of mehmast

ASKER

thanks for the help