DataRow.SetField() 在将数据添加到我之前删除然后重新添加的列时给出 null ref 异常

DataRow.SetField() gives a null ref exception when adding data to a column I previously deleted then added back

提问人:Kotorfreak 提问时间:6/29/2022 最后编辑:Kotorfreak 更新时间:6/30/2022 访问量:341

问:

更新
:我想我已经找到了导致这里问题的原因 https://stackoverflow.com/a/5665600/19393524
我相信我的问题在于我对 .该帖子认为,当您对它进行排序时,从技术上讲,它是对对象的写入操作,并且可能无法正确或完全传播所做的更改。这是一个有趣的读物,似乎回答了我的问题,即为什么在我对数据表进行更改,将有效数据传递给 a 会引发此异常
.DefaultViewDataTableDataRow

更新:让我说得很清楚。我已经解决了我的问题。我只想知道为什么它会抛出错误。在我看来,代码应该可以工作,而且确实如此。第一次运行。

我已经删除了该列,然后将其添加回来(运行此代码一次)

当我在 Visiual studio 中逐行调试代码并停在该行时:data.Rows[i].SetField(sortColumnNames[k], value);

  • 该行存在
  • 该色谱柱存在
  • value不为 null
  • sortColumnNames[k]不为 null 且包含正确的列名
  • i是 0
    但它仍然会抛出异常。我想知道为什么。我错过了什么?

很抱歉解释太长,但不幸的是,这需要一些上下文。

所以我的问题是,我有按列对 DataTable 对象中的数据进行排序的代码。用户选择他们想要排序的列,然后我的代码对其进行排序。

我遇到了一个问题,我需要数字作为数字而不是字符串进行排序(表中的所有数据都是字符串)。例如(字符串排序将导致 1000 在 500 之前)

因此,我的解决方案是创建一个使用正确数据类型的临时列,以便正确排序数字,并且数字的原始字符串数据保持不变,但现在已正确排序。这很完美。我可以将字符串数值数据作为数值数据进行排序,而无需更改数字或数据类型的格式。

我删除了之后用于排序的列,因为我使用 defaultview 对数据进行排序并将其复制到另一个 DataTable 对象。

这部分第一次就工作正常。

问题在于用户需要对同一列执行不同的排序。我的代码将该列添加回去。(同名)然后尝试向列添加值,但随后我得到一个空引用异常“对象未设置为对象的实例”

这是我尝试过的:

  • 我尝试在删除列后使用 AcceptChanges(),但这没有任何作用。
  • 我尝试在 SetField() 的第一个参数中使用 DataTable.Columns.Add() 返回的列索引、名称和列对象,以防它以某种方式引用我删除的“旧”列对象(这就是我认为问题很可能出现的原因)
  • 我尝试更改 .ItemArray[],但这甚至第一次都不起作用

代码如下:

以下是列名的传递方式:

private void SortByColumn()
        {
            if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
            {
                //clears the datatable object that stores the sorted defaultview
                sortedData.Clear();

                //grabs column names the user has selected to sort by and copies them to a string[]
                string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
                lbColumnsToSortBy.Items.CopyTo(lbItems, 0);

                //adds temp columns to data to sort numerical strings properly
                string[] itemsToSort = AddSortColumns(lbItems);         
                
                //creates parameters for defaultview sort
                string columnsToSortBy = String.Join(",", itemsToSort);
                string sortDirection = cbAscDesc.SelectedItem.ToString();
                data.DefaultView.Sort = columnsToSortBy + " " + sortDirection;

                //copies the defaultview to the sorted table object
                sortedData = data.DefaultView.ToTable();
                RemoveSortColumns(itemsToSort);//removes temp sorting columns
            }
        }

这是添加临时列的位置:

 private string[] AddSortColumns(string[] items)//adds columns to data that will be used to sort
                                                       //(ensures numbers are sorted as numbers and strings are sorted as strings)
        {
            string[] sortColumnNames = new string[items.Length];
            for (int k = 0; k < items.Length; k++)
            {
                int indexOfOrginialColumn = Array.IndexOf(columns, items[k]);
                Type datatype = CheckDataType(indexOfOrginialColumn);
                if (datatype == typeof(double))
                {
                    sortColumnNames[k] = items[k] + "Sort";

                    data.Columns.Add(sortColumnNames[k], typeof(double)); 

                    for (int i = 0; i < data.Rows.Count; i++)
                    {
                        //these three lines add the values in the original column to the column used to sort formated to the proper datatype
                        NumberStyles styles = NumberStyles.Any;
                        double value = double.Parse(data.Rows[i].Field<string>(indexOfOrginialColumn), styles);
                        bool test = data.Columns.Contains("QtySort");
                        data.Rows[i].SetField(sortColumnNames[k], value);//this is line that throws a null ref exception
                    }
                }
                else
                {
                    sortColumnNames[k] = items[k];
                }
            }
            return sortColumnNames;
        }

这是之后删除列的代码:

private void RemoveSortColumns(string[] columnsToRemove)
        {
            for (int i = 0; i < columnsToRemove.Length; i++)
            {
                if (columnsToRemove[i].Contains("Sort"))
                {
                    sortedData.Columns.Remove(columnsToRemove[i]); 
                }
            }
        }

注意:我已经能够通过保留列并从中删除列来解决问题,因为我在排序表上使用,这似乎可以确保不会抛出异常。datasortedData.Clear()

不过,我仍然想要一个答案,为什么这会引发异常。如果我在抛出异常的行之前使用,则表示该列存在并返回 true,如果有人想知道参数并且永远不会为 null。.Contains()sortColumnNames[k]value

C# 排序 NullReferenceException System.Data

评论

2赞 Jeroen Mostert 6/29/2022
您在枚举集合时正在更改集合,这始终是一个很大的禁忌。首先获取要删除的内容,将其单独存储,然后删除它(或者,在这种情况下,正如您发现的那样,只是不要更改集合,因为它无论如何都会在以后发生)。data.Columns
0赞 Kotorfreak 6/29/2022
@JeroenMostert我不确定你说的是我的代码的哪一部分,我仍在学习。在我已经对数据进行排序并将排序复制到 之后,我在辅助方法中删除了该列,而且我在第一次运行代码时工作得很好。只有在第二次运行代码时,才会引发异常。您能解释一下我在我的代码中枚举的位置,同时还更改了它吗?这将帮助我避免将来犯这个错误。sortedDatadata.Columns
1赞 Jeroen Mostert 6/29/2022
我只是在谈论 ,其中包含一个明显的错误(删除中间的列会使计数器与原始集合不同步)。诚然,我没有费心去深入挖掘你的其余代码,尽管那里的错误似乎很可能导致其余部分失败。这种模式基本上总是错误的,最好的情况是立即出现错误,最坏的情况是以后莫名其妙的失败。RemoveSortColumnsifor (...; i < collection.Count; ...) { /* change the number of items in `collection` in some way */ }
2赞 JohnG 6/29/2022
你说......“我遇到了一个问题,我需要数字作为数字而不是字符串进行排序(表中的所有数据都是字符串)。例如(字符串排序将导致 1000 排在 500 之前)“ ...在我看来,将数字存储为实际的“数字”而不是 .存储数字从来都不是一个好主意,正如您的问题所表明的那样,只能保证为您创造额外的工作。停止为自己创造不必要的工作,并将数字正确存储为数字。stringsstrings
0赞 Kotorfreak 6/29/2022
@JohnG我明白这一点。我使用的所有数据都来自 .txt 和 .csv 文件。如果它不改变数字的格式和显示方式,我会这样做。例如(0000090更改为 90)。我需要保留这种原始格式。之后我可以将数字重新格式化为字符串,但是我需要在转换前计算前导零并存储预先有多少,然后在它们最终导出到 .txt/.csv 文件时再次添加它们。这将使我的代码更加复杂,这正是我试图避免的。

答:

0赞 Cine 6/29/2022 #1

您的问题可能就在这里:

private void RemoveSortColumns()
        {
            for (int i = 0; i < data.Columns.Count; i++)
            {
                if (data.Columns[i].ColumnName.Contains("Sort"))
                {
                    data.Columns.RemoveAt(i);
                    sortedData.Columns.RemoveAt(i);
                }
            }
        }

如果您有 2 列,并且第一列与 匹配,则永远不会查看第二列。if

这是因为它将运行:

  • 我 = 0
  • 我是<列吗?计数为 2 => 是
  • 是 col[0]。包含(“排序”) true => yes
  • 删除 col[0]
  • 我 = 1
  • 我是<列吗?计数为 1 =>否

解决方案是在移除后重新调整i

private void RemoveSortColumns()
        {
            for (int i = 0; i < data.Columns.Count; i++)
            {
                if (data.Columns[i].ColumnName.Contains("Sort"))
                {
                    data.Columns.RemoveAt(i);
                    sortedData.Columns.RemoveAt(i);
                    i--;//removed 1 element, go back 1
                }
            }
        }

评论

0赞 Kotorfreak 6/29/2022
我只是尝试添加一个错误,但这仍然没有解决问题。我仍然得到相同的空引用异常。我更新了我的问题代码,以防止人们将来指出这一点i--
0赞 Jeroen Mostert 6/29/2022
这是一个解决方案,但它并不完全明显,对于某些专用集合,这种方法很容易失败,因为它假设项的相对顺序不会改变——这对于列集合是正确的,但不一定在一般情况下。更好的模式是首先在一个步骤中找到要删除的列,然后在下一步中删除所有列(即 )。var columnsToRemove = (from DataColumn c in data.Columns where c.ColumnName.Contains("Sort") select c.ColumnName).ToList(); foreach (var columnName in columnsToRemove) { data.Columns.Remove(columnName); }
-1赞 Kotorfreak 6/30/2022 #2

我通过更改方法中的几行代码来解决我的原始问题:SortByColumn()

private void SortByColumn()
        {
            if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
            {
                //clears the datatable object that stores the sorted defaultview
                sortedData.Clear();

                //grabs column names the user has selected to sort by and copies them to a string[]
                string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
                lbColumnsToSortBy.Items.CopyTo(lbItems, 0);

                //adds temp columns to data to sort numerical strings properly
                string[] itemsToSort = AddSortColumns(lbItems);         
                
                //creates parameters for defaultview sort
                string columnsToSortBy = String.Join(",", itemsToSort);
                string sortDirection = cbAscDesc.SelectedItem.ToString();
                DataView userSelectedSort = data.AsDataView();
                userSelectedSort.Sort = columnsToSortBy + " " + sortDirection;

                //copies the defaultview to the sorted table object
                sortedData = userSelectedSort.ToTable();
                RemoveSortColumns(itemsToSort);//removes temp sorting columns
            }
        }

我没有进行排序,而是创建一个新对象并传递它的值,然后对其进行排序。完全摆脱了我原始代码中的问题。对于任何想知道的人,我仍然认为这是 Microsoft 可能永远不会修复的 .NET 框架中的错误。我希望这将帮助将来遇到类似问题的人。
这里再次是我找到问题解决方案的链接。
https://stackoverflow.com/a/5665600
data.DefaultViewDataViewdata.AsDataView().DefaultView