DataTable内部索引已损坏

我正在使用C#中的.NET WinForms应用程序,运行3.5 .NET框架。 在这个应用程序中,我在DataTable设置DataColumn的.Expression成员,如下所示:

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

实际设置Expression的第二行有时会导致以下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) 

关于错误何时发生,没有可察觉的押韵或理由; 在加载相同的数据集时,它可能工作正常,但重新加载它将失败,反之亦然。 这让我认为它与竞争条件有关,当我试图修改其中一个列时, DataTable上正在发生另一个写操作。 但是,与DataTable相关的代码不是multithreading的,只能在UI线程上运行。

我搜索过网络和微软论坛 ,对这个问题有很多讨论和困惑。 回到2006年第一次报告这个问题的时候,人们认为它是.NET框架中的一个缺陷,并且有一些假定的修补程序可能已经发布到.NET框架的更高版本中。 但是,人们在应用这些修补程序时报告了不同的结果,这些修补程序不再适用于当前框架。

另一个流行的理论是,DataTable上有一些操作,虽然看似无害,但实际上是写操作。 例如,基于DataTable创建新的DataView实际上是对表本身的写操作,因为它在DataTable创建了一个内部索引供以后参考。 这些写操作不是线程安全的,因此有时会发生竞争条件导致与我们访问DataTable一致的非线程安全写入。 反过来,这会导致DataTable的内部索引损坏,从而导致exception。

我已经尝试在代码中的每个DataView创建周围放置lock块,但是,正如我之前提到的,使用DataTable代码没有线程化,并且lock无论如何都没有效果。

有没有人看过这个并成功解决/解决它?


不,不幸的是我做不到。 加载DataTable已经发生,当我抓住它以将Expression应用于其中一个DataColumn时。 我可以删除列,然后使用您建议的代码重新添加它,但有一个特殊的原因,为什么这将解决内部索引已损坏的问题?

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

编辑:事实certificate,这只是在一侧修复它,现在添加行会抛出此exception。

编辑2: 罗伯特罗斯尼建议的暂停绑定也为我解决了添加问题。 我只是从DataGridView删除了DataSource ,并在完成DataTable后对其进行了重新读取。

编辑3:仍然没有修复…自从星期四以来,我的代码中的exception一直在爬行…这是到目前为止我在框架中遇到的最奇怪和最常见的错误(在我使用.NET 2.0的3年中,我看到了许多奇怪的事情,足以保证我未来的项目不会在其上构建。) 但足够的咆哮,回到主题。

我已经阅读了Microsoft支持论坛上的整个讨论,我将为您简要介绍一下。 最初的错误报告起源于’05 。

  • 06年3月: Bug首次报道,调查开始。 在整个明年的过程中,据报道它有不同的forms和不同的表现forms。
  • 2003年3月:最后发布了一个编号为KB 932491的修补程序 (不要让你满意 ),它链接下载一个完全不相关的外观修补程序 ,或者至少看起来如此。 在接下来的几个月中,许多人报告说这个修补程序不起作用 ,有些报告成功。
  • 2007年7月:微软的最后一次现场直播(完全无用的答案),超出这一点,微软没有做出进一步的回应。 没有进一步的确认,没有尝试支持,没有更多信息的请求……没有。 除此之外,只有社区相关信息。

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

  • DataTable 不是线程安全的。 如果您在其上的任何位置有multithreading,则必须自己Lock / Synchronize它。
  • 索引的损坏发生抛出实际exception之前的某个地方。
  • 一种可能的损坏源是应用的Expression或应用的Sort
  • 另一个可能的来源是DataTable.ListChanged()事件,不要修改此事件中的数据或从中生成的任何事件。 这包括来自绑定控件的不同Changed事件。
  • DefaultView绑定到控件时可能会出现问题。 始终使用DataTable.BeginLoadData()DataTable.EndLoadData()
  • 创建和操作DefaultView是对DataTable (及其Index )的书写操作 ,Flying Spaghetti Monster知道原因。

可能的来源很可能是竞争条件,无论是在我们的源代码中还是在框架的代码中。 看来,微软无法修复此错误或无意。 无论哪种方式,检查您的代码是否有竞争条件,我认为它与DefaultView有关。 在某些时候, Insert或操纵数据会破坏内部索引,因为更改未通过整个DataTable正确传播。

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

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

就个人而言,这个特殊的错误已成为我各种时尚的3周克星。 我已经在我的代码库的一部分解决了它,它出现在其他地方(我相信我终于在今晚压制它)。 exception信息相当无益,并且由于缺少MS来解决问题,因此强制重新索引的方法将是一个很好的function。

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

好的 – 足够的抱怨。 让我们看看在我遇到过的各个地方解决这个问题的实际帮助我:

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

    if(column.Expression!=“some expression”)column.Expression =“some expression”;

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

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

你提到“not threadsafe”。 你在不同的线程中操纵对象吗? 如果是这样,那很可能是腐败的原因。

对于那些试图了解如何重现这个错误的人来说只是一个注释。 我有一些代码会经常产生错误。 它确实锁定并发读/写,但对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 exceptions = new ConcurrentDictionary(); 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.
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.
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.
b__8(Int32 i, ParallelLoopState s) in Program.cs:line 110 Press any key to continue . . .

如果你确实把锁放在FindRows调用上,没有例外。

我对这个问题的长期和痛苦的讨价还价的理解是,它是非线程安全写操作的工件,通常你不知道自己在做什么。

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

另一个潜在的麻烦来源是DataTable的RowChanging事件。 如果你做了一些修改该事件处理程序中的表的东西,那么就要做坏事。

同样的问题在这里,尝试了不同的方法。 我没有将数据表用于任何屏幕相关的东西(例如绑定); 我只是创建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); } } } } 

如何尝试应用这里描述的互斥锁的想法,以在这种情况下诱导线程中的睡眠?

这是我修复内部索引是如何被破坏的问题:

 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

我通过这种方式解决了我的datatable-internal-index错误:

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 

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

你不能只使用:

 dtData.Columns.Add("TestColumn", typeof(Decimal), "Price * Quantity"); 

同样的事也发生在我身上。 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 } 

在我的例子中,Framework版本是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值)。

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

  • 只有当e.ListChangedType == ListChangedType.ItemAdded时,才能更改ListChanged事件中的数据。
  • 在设置列值之前,应该执行检查以确保这是第一个ItemAdded事件(例如,如果第二次调用时值不能为null,请检查它是否为DBNull.Value等)

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

事实certificate,我已经改变了网格中列的顺序,不知道DataGridView.CellValidated子中的代码是否会导致该特定列中的值导致问题。

该代码引用了特定的列号。 因此,当原始DataGridView列3被移动并成为第1列,但DataGridView.CellValidated代码仍然引用第3列时,发生了错误。 更改我们的代码,以便它引用正确的e.ColumnIndex似乎解决了我们的问题。

(要想在我们的代码中更改这一个数字并不容易。我希望这个解决方案成立。)

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

试试这个..

 SyncLock your datatable '''' ----your datatable process End SyncLock 

我使用Threads遇到了同样的问题。 我所做的是创建一个委托,当我需要合并表时调用该委托。

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

然后在执行期间我调用委托并且不会发生错误。

 Delegates.MergeData mergeData = new Delegates.MergeData (Delegates.MergeDataTable); object [] paramsMerge = {dataTable1, dataTable2}; this.Invoke (mergeData, paramsMerge); 

我遇到了同样的问题,这就是为我解决的问题: Stack Overflow – 内部索引已损坏 。

如果您使用带有数据集的线程,则会发生该错误。

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

一种方法是使用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方法中,我在线程中进行了大量的计算。

希望这可以帮助。