31

I am working with a .NET WinForms app in C#, running against the 3.5 .NET framework. In this app, I am setting the .Expression member of a DataColumn in a DataTable, like so:

DataColumn column = dtData.Columns["TestColumn"];
column.Expression = "some expression";

The 2nd line, where I actually set Expression, will sometimes result in the following exception:

FileName=
LineNumber=0
Source=System.Data
TargetSite=Int32 RBInsert(Int32, Int32, Int32, Int32, Boolean)
System.InvalidOperationException: DataTable internal index is corrupted: '5'.
   at System.Data.RBTree`1.RBInsert(Int32 root_id, Int32 x_id, Int32 mainTreeNodeID, Int32 position, Boolean append)
   at System.Data.RBTree`1.RBInsert(Int32 root_id, Int32 x_id, Int32 mainTreeNodeID, Int32 position, Boolean append)
   at System.Data.Index.InitRecords(IFilter filter)
   at System.Data.Index.Reset()
   at System.Data.DataTable.ResetInternalIndexes(DataColumn column)
   at System.Data.DataTable.EvaluateExpressions(DataColumn column)
   at System.Data.DataColumn.set_Expression(String value)

There is no perceptible rhyme or reason as to when the error will occur; in loading the same data set, it may work fine but then reloading it will fail, and vice versa. This leads me to think it is related to a race condition, where another write operation is occurring on the DataTable as I'm trying to modify one of its columns. However, the code relating to DataTables is not multi-threaded and runs only on the UI thread.

I have searched the web and Microsoft forums, and there is much discussion and confusion over this issue. Back when the issue was first reported in 2006, the thought was that it was an flaw in the .NET framework, and there were some supposed hotfixes released that were presumably rolled into later versions of the .NET framework. However, people have reported mixed results in applying those hotfixes, which are no longer applicable to the current framework.

Another prevailing theory is that there are operations on the DataTable which, though seemingly innocuous, are actually write operations. For example, creating a new DataView based on a DataTable is actually a write operation on the table itself, because it creates an internal index in the DataTable for later reference. These write operations are not thread-safe, so it sometimes happens that a race condition leads to an unthread-safe write coinciding with our access of the DataTable. This, in turn, causes the internal index of the DataTable to become corrupted, leading to the exception.

I have tried putting lock blocks around each DataView creation in the code, but, as I mentioned before, code utilizing the DataTable is not threaded, and the locks had no effect, in any case.

Has anyone seen this and successfully solved / worked around it?


No, unfortunately I can not. Loading the DataTable has already occurred by the time I get a hold of it to apply an Expression to one of its DataColumn's. I could remove the column and then re-add it using the code you suggested, but is there a particular reason why that would solve the internal index is corrupted problem?

4

18 回答 18

23

我在导入行时遇到了同样的问题,看起来,DataTable.BeginLoadData在插入为我修复它之前调用。

编辑:事实证明,这只在一侧修复了它,现在添加行会引发此异常。

Edit2:Robert Rossney建议的暂停绑定也为我解决了添加问题。我只是DataSource从 中删除DataGridView并在完成DataTable.

Edit3:仍然没有修复......自星期四以来,我的代码中所有不同的地方都出现了异常......这是迄今为止我在框架中遇到的最奇怪和最糟糕的错误(在我使用 .NET 2.0 的 3 年中,我看到了许多奇怪的事情,足以保证我未来的任何项目都不会建立在它之上)。不过废话不多说,回到正题。

我已经阅读了 Microsoft 支持论坛上的整个讨论,我会给你一个简短的总结。原始错误报告源自 '05

  • 06 年 3 月:第一次报告错误,开始调查。在接下来的一年中,它以不同的形式和不同的表现形式被报道。
  • 07 年 3 月:终于发布了一个编号为 KB 932491 的修补程序(不要抱太大希望),它链接到一个完全不相关的修补程序的下载,或者至少看起来如此。在接下来的几个月中,许多人报告说该修补程序不起作用,有些人报告说成功了。
  • 07 年 7 月:来自 Microsoft 的最后一个迹象(带有完全无用的答案),除此之外,Microsoft 没有进一步的回应。没有进一步的确认,没有尝试支持,没有要求提供更多信息……什么都没有。除此之外,只有社区相关信息。

不认真,这在我看来总结了它。我能够从整个讨论中提取以下信息:

  • DataTable不是线程安全的。如果你在任何地方都有多线程,你将不得不自己Lock/Synchronize它。
  • 索引的损坏发生在引发实际异常之前的某个地方。
  • 一种可能的损坏源是应用程序Expression或应用程序Sort
  • 另一个可能的来源是DataTable.ListChanged()事件,切勿修改此事件或由此产生的任何事件中的数据。这包括Changed来自绑定控件的不同事件。
  • DefaultView将与控件绑定时可能会出现问题。始终使用DataTable.BeginLoadData()DataTable.EndLoadData()
  • 的创建和操作DefaultView是对(及其)的写入操作,飞行意大利面怪物知道原因。DataTableIndex

这可能的来源很可能是竞争条件,无论是在我们的源代码中还是在框架的代码中。看起来,Microsoft 无法修复此错误或无意修复此错误。无论哪种方式,请检查您的代码中的竞争条件,DefaultView我认为这与 . 在某些时候Insert,数据的一个或一个操作会破坏内部索引,因为这些更改没有正确地传播到整个DataTable.

当我找到更多信息或其他修复时,我当然会报告。对不起,如果我在这里有点情绪化,但我花了三天时间试图查明这个问题,它慢慢开始看起来像是找一份新工作的好理由。

Edit4:我能够通过完全删除绑定(control.DataSource = null;)并在数据加载完成后重新添加它来避免这个错误。这激发了我的想法,即它与DefaultView绑定控件产生的事件有关。

于 2011-04-14T15:26:26.070 回答
11

就个人而言,这个特殊的错误已经以各种方式成为我 3 周的克星。我已经在我的代码库的一部分中解决了它,它出现在其他地方(我相信我今晚终于把它压扁了)。异常信息相当无用,鉴于缺乏 MS 来解决问题,强制重新索引的方法将是一个不错的功能。

我不会寻找 MS 的修补程序——他们有一篇关于它的知识库文章,然后将您重定向到一个完全不相关的 ASP.Net 修复程序。

好吧——抱怨够多了。让我们看看在我遇到的各个地方实际帮助我解决了这个特定问题:

  • 避免使用默认视图,并尽可能修改默认视图。顺便说一句,.Net 2.0 在创建视图时有许多读/写锁,所以它们不是 2.0 之前的问题。
  • 尽可能调用 AcceptChanges()。
  • 小心 .Select(expression),因为此代码中没有读取器/写入器锁——而且它是唯一的地方(至少根据 usenet 上的一个人的说法,所以把它带上一粒盐——但是,这与您的问题非常相似-因此使用互斥锁可能会有所帮助)
  • 将 AllowDBNull 设置为有问题的列(有问题的值,但在 usenet 上报告——我只在有意义的地方使用它)
  • 确保您没有将 null (C#)/Nothing (VB) 设置为 DataRow 字段。使用 DBNull.Value 而不是 null。在您的情况下,您可能希望检查该字段是否不为空,表达式语法确实支持 IsNull(val, alt_val) 运算符。
  • 这可能对我帮助最大(听起来很荒谬):如果一个值没有改变,不要分配它。因此,在您的情况下,请使用它而不是您的直接分配:

    if (column.Expression != "some expression") column.Expression = "some expression";

(我删除了方括号,不知道他们为什么在那里)。

编辑(2012 年 5 月 16 日):刚刚反复遇到这个问题(使用 UltraGrid/UltraWinGrid)。使用删除 DataView 上的排序的建议,然后添加一个与 DataView 排序匹配的排序列,这解决了问题。

于 2009-01-29T05:22:27.987 回答
4

你提到“不是线程安全的”。您是否正在从不同的线程操作对象?如果是这样,那么这很可能是腐败的原因。

于 2009-01-17T16:19:26.887 回答
4

对于那些试图查看如何重现此错误的人来说,这只是一个说明。我有一些代码经常会产生该错误。它确实围绕并发读/写锁定,但对 DataView.FindRows 的调用是在该锁定之外完成的。OP 指出创建数据视图是一种隐藏的写入操作,是否也可以查询它?

//based off of code at http://support.microsoft.com/kb/932491
using System.Data;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System;
public class GenerateSomeDataTableErrors
{   
    public static void Main()
    {
        DataTable Table = new DataTable("Employee");
        Table.Columns.Add("Id", typeof(int));
        Table.Columns.Add("Name", typeof(string));
        Table.PrimaryKey = new DataColumn[] { Table.Columns["Id"] };

        DataSet Employees = new DataSet();
        Employees.Tables.Add(Table);

        DataRow ManagerB = Table.NewRow();
        ManagerB["ID"] = 392;
        ManagerB["Name"] = "somename";
        Table.Rows.Add(ManagerB);

        DataRow ManagerA = Table.NewRow();
        ManagerA["ID"] = 394;
        ManagerA["Name"] = "somename";
        Table.Rows.Add(ManagerA);

        Employees.AcceptChanges();

        object locker = new object();

        //key = exception string, value = count of exceptions with same text
        ConcurrentDictionary<string, int> exceptions = new ConcurrentDictionary<string, int>();

        DataView employeeNameView = new DataView(Table, string.Empty, "Name", DataViewRowState.CurrentRows);

        Parallel.For(0, 100000, (i, s) =>
        {
            try
            {
                #region do modifications to the table, in a thread-safe fashion
                lock (locker)
                {
                    var row = Table.Rows.Find(392);

                    if (row != null) //it's there, delete it
                    {
                        row.Delete();
                        Employees.AcceptChanges();
                    }
                    else //it's not there, add it
                    {
                        var newRow = Table.NewRow();
                        newRow["ID"] = 392;
                        newRow["Name"] = "somename";
                        Table.Rows.Add(newRow);
                        Employees.AcceptChanges();
                    }
                }
                #endregion

                //Apparently this is the dangerous part, finding rows 
                // without locking on the same object the modification work is using.
                //lock(locker)
                employeeNameView.FindRows("somename");
            }
            catch (Exception e)
            {
                string estring = e.ToString();
                exceptions.TryAdd(estring, 0);
                lock (exceptions)
                { exceptions[estring] += 1; }
            }
        });

        foreach (var entry in exceptions)
        {
            Console.WriteLine("==============The following occurred " + entry.Value + " times");
            Console.WriteLine(entry.Key);
        }
    }//Main
}//class

如果您按原样运行它,您可以获得这样的输出(每次运行时输出都会有所不同):

==============The following occurred 2 times
System.InvalidOperationException: DataTable internal index is corrupted: '13'.
   at System.Data.RBTree`1.GetNodeByIndex(Int32 userIndex)
   at System.Data.DataView.GetRow(Int32 index)
   at System.Data.DataView.GetDataRowViewFromRange(Range range)
   at System.Data.DataView.FindRowsByKey(Object[] key)
   at GenerateSomeDataTableErrors.<>c__DisplayClass9.<Main>b__8(Int32 i, ParallelLoopState s) in Program.cs:line 110
==============The following occurred 3 times
System.IndexOutOfRangeException: Index 1 is either negative or above rows count.
   at System.Data.DataView.GetRow(Int32 index)
   at System.Data.DataView.GetDataRowViewFromRange(Range range)
   at System.Data.DataView.FindRowsByKey(Object[] key)
   at GenerateSomeDataTableErrors.<>c__DisplayClass9.<Main>b__8(Int32 i, ParallelLoopState s) in line 110
==============The following occurred 1 times
System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Data.DataView.GetRow(Int32 index)
   at System.Data.DataView.GetDataRowViewFromRange(Range range)
   at System.Data.DataView.FindRowsByKey(Object[] key)
   at GenerateSomeDataTableErrors.<>c__DisplayClass9.<Main>b__8(Int32 i, ParallelLoopState s) in Program.cs:line 110
Press any key to continue . . .

如果您确实在 FindRows 调用上加了锁,则没有例外。

于 2012-08-14T18:26:13.840 回答
2

从对这个问题的长期和痛苦的讨价还价中,我的理解是,它是非线程安全的写操作的产物,通常你不知道你正在制作它。

就我而言,罪魁祸首似乎是 BindingSource。我发现我需要暂停绑定,执行我尝试的任何操作,然后在完成后恢复绑定,问题就消失了。这是 18 个月前的事了,所以我不再清楚细节,但我记得得到的印象是 BindingSource 正在自己的线程上执行某种操作。(这对我来说现在比当时更有意义。)

另一个潜在的麻烦来源是 DataTable 的 RowChanging 事件。如果您做了一些修改该事件处理程序中的表的操作,那么会出现不好的情况。

于 2009-03-19T18:18:18.817 回答
1

这就是我修复内部索引损坏问题的方法:

System.Data.DataTable dtNew = new DataTable();
for (int iCol = 0; iCol < dtOriginalData.Columns.Count; iCol++)
{
    dtNew.Columns.Add(dtOriginalData.Columns[iCol].ColumnName, dtOriginalData.Columns[iCol].DataType);
}
for (int iCopyIndex = 0; iCopyIndex < item.Data.Rows.Count; iCopyIndex++)
{
    dtNew.Rows.Add(dtOriginalData.Rows[iCopyIndex].ItemArray);
    //dtNew.ImportRow(dtOriginalData.Rows[iCopyIndex]); 
}
dtOriginalData = dtNew; 

享受吧,安德鲁 M

于 2012-03-23T09:08:50.143 回答
1

同样的问题在这里,并尝试了不同的方法。我没有将数据表用于任何与屏幕相关的内容(例如绑定);我只是创建 DataRow 对象(在多个线程中)并将它们添加到表中。

我尝试过使用 lock(),还尝试将添加的行集中到一个单例中,认为这会有所帮助。它没有。作为参考,这是我使用的单例。也许其他人将能够在此基础上构建并解决一些问题?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace EntityToDataSet
{
   public class RowAdder
   {
      #region Data
      private readonly object mLockObject = new object();
      private static RowAdder mInstance;

      public static RowAdder Instance
      {
         get
         {
            if (mInstance == null)
            {
               mInstance = new RowAdder();
            }
            return mInstance;
         }
      }

      object mSync;
      #endregion

      #region Constructor
      private RowAdder()
      {
      }
      #endregion

      public void Add(DataTable table, DataRow row)
      {
         lock (mLockObject)
         {
            table.Rows.Add(row);
         }
      }
   }
}
于 2011-05-18T20:53:05.110 回答
1

在这种情况下尝试应用此处描述的互斥锁以 在线程中诱导睡眠的想法怎么样?

于 2011-06-03T10:10:21.623 回答
1

我以这种方式解决了我的数据表内部索引错误:

改为CellEndEdit事件CellBeginEdit。另外...避免不必要地使用NULL:

Private Sub User_role_groupDataGridView_CellBeginEdit(sender As Object, e As DataGridViewCellCancelEventArgs) Handles User_role_groupDataGridView.CellBeginEdit
    Try 
        If Not Me.User_role_groupDataGridView.Rows(e.RowIndex).IsNewRow Then Me.User_role_groupDataGridView.Rows(e.RowIndex).Cells("last_modified_user_group_role").Value = Now
    Catch ex As Exception
        Me.displayUserMessage(ex.ToString, Me.Text, True)
    End Try
End Sub
于 2015-12-02T17:31:02.437 回答
1

在以编程方式将行添加到绑定到 datagridview 的数据集时,我遇到了同样的问题(表索引损坏为 5)。我没有考虑到 datagridview 的 AddRow 事件上有一个事件处理程序,它会在用户通过 UI 启动新行的情况下进行一些初始化。在异常堆栈跟踪中没有看到它。通过禁用该事件,我可以快速解决这个问题。我只是通过深入阅读这里的一些评论来了解它。对于这样的问题,2 小时不算多 :-),我认为。您可以通过在分配给链接到数据集的 datgridview 的每个事件处理程序中设置断点来找到它。

于 2016-02-21T00:37:06.090 回答
1

我遇到了同样的问题,这就是为我解决的问题:堆栈溢出 - 内部索引已损坏

如果您将线程与数据集一起使用,则会发生该错误。

就我而言,我试图在线程中运行的方法中为数据集创建一个新行。

一种方法是在创建行的方法周围使用SyncLock ,或者另一种方法(可能甚至更好)是在线程之外创建行。

基本上我的代码看起来像这样:

    Dim elements As New List(Of element)
    Dim dataRows As New List(Of MyDataSet.Row)

    For i As Integer = 0 To elements.Count - 1
        dataRows.Add(Me.Ds.Elements.NewElementsRow)
    Next

    Parallel.For(0, elements.Count, Sub(i As Integer)
                                        Me.CreateElementRow(elements(i), dataRows(i))
                                    End Sub)

CreateElementRow方法中,我在线程中进行了大量计算。

希望这可以帮助。

于 2015-12-08T11:12:06.960 回答
0

同样的事情也发生在我身上。Winforms,.NET 3.5,在尝试设置键入行中的一列时意外出现此错误。代码相当陈旧并且工作了很长时间,所以这有点令人不快......

我需要在数据集 TadaSet 的类型表 TadaTable 中设置新的 SortNo。

对我有什么帮助,你也可以试试这个:

int i = 0;
foreach (TadaSet.TadaTableRow row in source)
{
     row.BeginEdit(); //kinda magical operation but it helped :)
     // Also you can make EndEdit() for each row later if you need...
     short newNo = i++;
     if (newNo != row.SortNo) row.SortNo = newNo; //here was the crash
}
于 2011-11-22T12:14:45.640 回答
0

这似乎对我的同事 Karen 和我有用。我们在 DataGridView 中遇到了这个错误,但仅在将数据输入到一个特定列时才出现。

事实证明,我已经更改了网格中列的顺序,但不知道 DataGridView.CellValidated 子中的代码将导致问题的特定列中的值归零。

该代码引用了特定的列号。所以当原来的DataGridView第3列被移动变成了第1列,但DataGridView.CellValidated代码仍然引用第3列时,就出现了错误。更改我们的代码以使其引用正确的 e.ColumnIndex 似乎解决了我们的问题。

(在我们的代码中改变这个数字并不容易。我希望这个修复能成立。)

于 2013-05-01T20:15:44.770 回答
0

你不能只使用:

dtData.Columns.Add("TestColumn", typeof(Decimal), "Price * Quantity");
于 2009-01-16T20:50:49.290 回答
0

可能是您同时在多个进程中使用相同的数据表。我刚刚使用SYNCLOCK...解决了这个问题

尝试这个..

SyncLock your datatable

'''' ----your datatable process

End SyncLock
于 2012-04-20T13:33:53.203 回答
0

在我的情况下,框架版本是 2.0。问题的根源在于 DataView ListChanged 事件。下面的代码使用一些默认值初始化新行。

private void dataView_ListChanged(object sender, ListChangedEventArgs e)
{
    if (e.ListChangedType == ListChangedType.ItemAdded)
    {
        DataView v = (DataView)sender;
        DataRowView drv = v[e.NewIndex];

        // This "if" works fine
        if (drv["Foo"] == DBNull.Value)
        {
            drv["Foo"] = GetFooDefault();
        }

        // This "if" brakes the internal index     
        if (drv["Bar"] == DBNull.Value && drv["Buz"] != DBNull.Value)
        {
            drv["Bar"] = drv["Buz"];
        }
    }
}

经过一番调查,很明显 ItemAdded 事件每行至少调用两次。第一次当 UI 创建新行以输入数据时,第二次,我不确定,但看起来像将 DataRowView 添加到 DataView 时。

第一个“if”仅在第一次调用 ItemAdded 时才有效。在第二次调用时,“Foo”列已填充并保持原样。

但是,“Bar”列的默认代码可以在两个调用中执行。实际上,在我的情况下,它仅在第二个 ItemAdded 事件中执行,当时用户有机会填写“Buz”列的数据(最初“Buz”具有 DBNull 值)。

所以这里是基于我的发现的建议:

  • ListChanged 事件中的数据只有在e.ListChangedType == ListChangedType.ItemAdded.
  • 在设置列值之前,应执行检查以确保这是第一个 ItemAdded 事件(例如,如果在第二次调用时该值不能为空,请检查它是否为DBNull.Value等)
于 2012-05-30T08:33:05.597 回答
0

I had the same problem using Threads. What I did was create a delegate which is called when I need to merge the table.

internal delegate void MergeData (DataTable dataTable1, DataTable dataTable2);

internal static void MergeDataTable (DataTable dataTable1, DataTable dataTable2)
{
    dataTable1.Merge (dataTable2, true);
}

Then during execution I call the delegate and the error does not occur.

Delegates.MergeData mergeData = new Delegates.MergeData (Delegates.MergeDataTable);

object [] paramsMerge = {dataTable1, dataTable2};

this.Invoke (mergeData, paramsMerge);
于 2017-12-29T13:25:04.223 回答
0

与 .NET 4.5.2 win forms 应用程序相同。在我的情况下,原因似乎是多个控件绑定到单个 BindingSource 列。我知道将许多控件绑定到一个值是在自找麻烦,但由于布局相当复杂,这对我来说是一个较小的邪恶。

似乎从一侧更改值引发了多个 OnChange 事件,试图对 BindingSource 执行相同的操作,从而导致出现问题。

我试过暂停绑定但没有成功。我已经实现了一些标志,防止代码被并行执行多次,事情变得更好了。

于 2019-06-13T08:34:30.667 回答