表示集合中数据的表的最佳方式

时间:2011-10-07 07:43:23

标签: .net collections

我认为我的问题是经典问题,但我无法找到实施解决方案的正确方法。所以我会在SOF问这里。

我需要将一些数据导出到CSV文件,其中包含标题和值,如表格。要获取数据,我需要迭代一个集合。集合中的每个项目都有一个属性,其中包含一组键/值对。该键包含标题。并非每个键/值对的集合都具有相同的大小。

在遍历键/值对时,我会收集集合中所有可能的标题。当您到达其他键/值集合并找到未知密钥时,将扩展此集合。发生这种情况时,您需要确保相应的值将写入CSV文件中的右侧标题下。我试图在标题集合中使用标题的索引,但我似乎无法让它工作。我已经考虑过多维数组,锯齿状数组和几个字典组合。

对于我的解决方案,我不希望在查找正确的列时在标题和键之间进行字符串比较。这似乎没必要。我认为应该做两个循环和索引。

好的,这是我到目前为止的代码。这仅适用于外部循环中的1个项目,因为ArrayList一次只消耗一个而不消耗任何给定的索引。当在外部循环中的索引12处添加到头集合的新项时,循环的值集合还没有索引12。所以我得到一个超出范围的索引。所以我想创建一个带有标题arraylist大小的值arraylist,但这也不起作用。

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

我认为这个问题已经解决了一千次,所以请善待。

更新:如果你有任何其他(主流)语言的答案而不是vb.net,那也没关系,但我更喜欢vb.net和C#,因为它们都在.net上框架。

由于

2 个答案:

答案 0 :(得分:2)

首先,我认为你应该将你的功能分成更小的块。其次,你应该问问自己,为什么你仍然在使用ArrayList,而你可以使用泛型来使用和使用。

你似乎在arraylists中存储arraylists,这已经是你需要一个类的一个标志,然后是一个类的列表,如

Public Class ValueStore
  Public Property Index as Integer
  Public Property FieldValue as String
End Class

然后你可以像这样有一个ResutlStore类

Public Class ResultStore
  Public ReadOnly Property ValueStores as Ilist(Of ValueStore)
  Public Sub New()
    ValueStores = new List(Of ValueStore)
  End Sub
  Public Sub AddValueStore(Byval Index as Integer, FieldValue as Integer)
    ValueStores.Add(new ValueStore() With {.Index = Index, .FieldValue = FieldValue})
  End Sub
End Class

之后你会做一些提取方法

例如,你可以将这整个事情踢出它自己的方法

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

喜欢这个。

Private Shared Function HeaderIndex(Byval fieldvaluekey as object) as integer
    Dim displaynames = ConfigurationService.FieldValueDisplayNames
    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
      return headerIndex
  End Function

这将使您的功能更具可读性

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

        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
                    valuesToStore.Insert(headerIndex(fieldValue.Key), fieldValue.Value.ToString) 'use headerindex to match key an value
                End If
            Next
            resultsToStore.Add(valuesToStore) 
        Next
        Return resultsToStore
    End Function

答案 1 :(得分:0)

首先,我要感谢Chrissie1鼓励我得到答案。对于其他人来说,这是我的解决方案。

首先,我创建了一个类来表示结果csv文件中的每一行。

Public Class MetaDataValue
     Inherits SortedDictionary(Of Integer, String)

     ''' <summary>
     ''' creates a comma seperated string of all data
     ''' </summary>
     ''' <returns></returns>
     ''' <remarks></remarks>
     Public Overrides Function ToString() As String
           ' some code to create a comma separated string of all data contained in the sorted    dictionary
     End Function
End Class

我使用了排序字典,因为它对键上的所有数据进行排序,这是一个整数。这样您就可以确保所有数据都在索引上排序。此外,使用字典提供了一种向集合添加数据而不会使索引超出边界错误的方法。

之后,您需要一种方法来收集所有metadataValues。所以我创建了一个类:

''' <summary>
''' This class respresents a listItemCollection as provided in the constructor as table like data with headers and rows
''' </summary>
''' <remarks></remarks>
Public Class MetadataValuesFactory
    Private _fieldsNotToStore As List(Of String) = ConfigurationService.FieldValuesNotToStore
    Private _headers As MetaDataValue = New MetaDataValue()
    Private _rows As IList(Of MetaDataValue) = New List(Of MetaDataValue)

    ''' <summary>
    ''' returns a metaDAtvaleu object that contains all header values
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>

    Public Function GetHeaders() As MetaDataValue
        Dim result As MetaDataValue = New MetaDataValue()
        Dim displaynames = ConfigurationService.FieldValueDisplayNames
        For Each item In Me._headers
            Dim displayname = String.Empty
            If (displaynames.ContainsKey(item.Value)) Then
                result.Add(item.Key, displaynames.Item(item.Value))
            Else
                result.Add(item.Key, item.Value)
            End If
        Next
        Return result
    End Function

    ''' <summary>
    ''' Returns all rows that represent the values
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetRows() As IList(Of MetaDataValue)
        Return _rows
    End Function

    ''' <summary>
    ''' Creates a new metadatavaluesstore with the specified values
    ''' </summary>
    ''' <param name="listItems"></param>
    ''' <remarks></remarks>

    Sub New(listItems As ListItemCollection)
        'first store all values and make sure keys and values are matched
        Dim totalListItems = listItems.Count

        For index As Integer = 0 To totalListItems - 1
            Dim valuesToStore As MetaDataValue = New MetaDataValue()
            Dim item As SPClient.ListItem = listItems(index)
            Dim fieldValues = item.FieldValues

            For Each fieldValue In fieldValues
                If (Not _fieldsNotToStore.Contains(fieldValue.Key)) Then 'If it is not in this collection is must be stored
                    'Get index of field in _headers
                    Dim headerindex As Integer = _headers.Values.ToList().IndexOf(fieldValue.Key.ToString)
                    If (headerindex = -1) Then 'If fieldValue.Key is already in the array it doesn't need to be stored again (-1 = no index found)
                        'If Not exists then store header/key in _headers ánd set previous index to index of new headers value
                        headerindex = _headers.Count '' Add new header
                        _headers.Add(headerindex, fieldValue.Key.ToString)
                    End If
                    'add value to valuesstore
                    Dim valueToStore As String
                    If (fieldValue.Value Is Nothing) Then
                        valueToStore = String.Empty
                    Else
                        valueToStore = fieldValue.Value.ToString
                    End If
                    valuesToStore.Add(headerindex, valueToStore)
                End If
            Next
            _rows.Add(valuesToStore)
        Next
 End Sub
 End Class

N.B。我称之为工厂,我不太确定这是使用这种模式的正确方法。但它不能是(普通)模型,因为此代码使用配置服务。你也不能把它称为服务,所以我选择了工厂。

使用这两个类,您可以更轻松地使用工厂中包含的数据。例如,在我的原始解决方案中,我使用displaynames更改了一些值。像Chrissie1建议的那样可以重构。正如您所看到的,我现在在获取所有标头的方法中执行此操作。这种方式有点晚了;在内部使用原始值,而在外部仅显示工厂允许的值。这种方式逻辑包含在工厂内。如果在将来某些时候需要一些新功能,则可以轻松地分别访问标头和值。

编写CSV文件的代码现在更容易理解:

metaDataStreamWriter.WriteLine(metadataFactory.GetHeaders.ToString)
For Each storeValue In metadataFactory.GetRows
     metaDataStreamWriter.WriteLine(storeValue.ToString)
Next

无论如何这解决了我的问题。再次感谢您提供反馈和吸取教训。如果您有任何意见,请提供。

相关问题