I think my problem is a classic one, but I can’t get the right way of implementing the solution. So I will ask it here at SOF.
I need to export some data to a CSV file which contains headers and values like a table. To get the data I need to iterate through a collection. Each item in the collection has a property which contains a collection of key/value pairs. The key contains the header. Not every collection of key/value pairs is of the same size.
When iterating through the key/value pair I collect all possible headers in a collection. This collection will be extended when you get to an other collection of key/values and an unknown key has been found. When this happens you need to make sure that the corresponding value will be written under the right header in the CSV file. I was trying to use the index of the header in the header collection, but I cant seem to get it to work. I have thought about multidimensional array’s, jagged arrays and several combinations with dictionaries.
For my solution I don’t want to do string comparisons between headers and keys when looking for the right column. This seems unnecessary. Two loops and indices should do it I think.
OK, here’s my code I had so far. This only works for 1 item in the outer loop, because ArrayList expends only one at a time and not to any given index. When a new item to the header collection is added at index 12 in the outer loop, the values collection of the loop doesn’t have an index 12 yet. So I get an index out of bounds. So I thought creating a values arraylist with the size of the header arraylist, but that doesn’t work either.
Private Shared Function GetData(listItems As SPClient.ListItemCollection) As ArrayList
'first store all values and make sure keys and values are matched
Dim resultsToStore As ArrayList = New ArrayList()
Dim headersToStore As ArrayList = New ArrayList()
resultsToStore.Add(headersToStore)
Dim totalListItems = listItems.Count
Dim fieldNotToStore = ConfigurationService.FieldValuesNotToStore
Dim displaynames = ConfigurationService.FieldValueDisplayNames
For index As Integer = 0 To totalListItems - 1
Dim valuesToStore As ArrayList = New ArrayList()
Dim item As SPClient.ListItem = listItems(index)
Dim fieldValues = item.FieldValues
For Each fieldValue In fieldValues
If (Not fieldNotToStore.Contains(fieldValue.Key)) Then 'If it is not in this collection is must be stored
Dim headerIndex = headersToStore.IndexOf(fieldValue.Key) 'does this key exist in the headersArray
If (headerIndex = -1) Then 'If fieldValue.Key is already in the array it doesn't need to be stored again (-1 = no index found)
Dim displayname = String.Empty
If (displaynames.ContainsKey(fieldValue.Key)) Then
displayname = displaynames.Item(fieldValue.Key)
Else
displayname = fieldValue.Key.ToString
End If
headerIndex = headersToStore.Add(displayname) '' Add new header
End If
valuesToStore.Insert(headerIndex, fieldValue.Value.ToString) 'use headerindex to match key an value
End If
Next
resultsToStore.Add(valuesToStore)
Next
Return resultsToStore
End Function
I think this problem has been solved like a thousand times, so please be kind.
Update: If you have an answer in any other (mainstream) language than vb.net, that is OK too, but I prefer vb.net and C# as they are both on the .net framework.
Thanks
First of All I want to thank Chrissie1 for inspiring me to get the answer. For everyone else, here’s my solution.
First I created a class to represent each line in the result csv file.
I used a sorted dictionary because that sorts all data on the key which is an integer. This way you are sure all data is sorted on the index. Furthermore using a dictionary provides a way of adding data to a collection without getting index out of bounds errors.
After that you need a way to collect all metadataValues. So I created a class:
N.B. I called this a factory I’m not quite sure this is the right way to use this pattern. But it can’t be a (ordinary) model because this code uses the configurationservice. You can’t really call it a service either, so I settled for factory.
Using these two classes you can much more easily use the data contained in the factory. For example, in my original solution I changed some values with displaynames. Like Chrissie1 suggested this can be refactored. As you can see I now do this in a method that gets all headers. This way it is sort of late bound; internally the original values are used while on the outside only values are visible that the factory will allow. This way this logic is contained within the factory. If, at some point in the future some new functionality is needed, it is easy to access headers and values separately.
The code writing the CSV file is now much more understandable:
Anyway this solved my problem. Many thanks again for providing feedback and for a lesson learned. If you have some comments please provide.