DataTable内部索引已损坏

问题描述 投票:27回答:17

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

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

实际设置Expression的第二行有时会导致以下异常:

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上发生了另一个写操作。但是,与DataTables相关的代码不是多线程的,只能在UI线程上运行。

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

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

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

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


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

c# .net datatable
17个回答
20
投票

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

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

编辑2:Robert Rossney建议的暂停绑定也为我修复了添加问题。我简单地从DataSource中删除了DataGridView,并在完成DataTable后对其进行了重新读取。

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

我已经阅读了整个讨论at the Microsoft support forums,我会给你一个简短的总结。原来的bug report originates in '05

  • 06年3月:Bug首次报道,调查开始。在整个明年的过程中,据报道它有不同的形式和不同的表现形式。
  • 07年3月:最后a hotfix with number KB 932491被释放(不要让你的希望得到),它与a download of an completely irrelevant looking hotfix联系,或者至少看起来如此。在接下来的几个月中,许多人报告说这个修补程序不起作用,有些报告成功。
  • 07年7月:微软的最后一次现场直播(完全无用的答案),超出这一点,微软没有做出进一步的回应。没有进一步的确认,没有尝试支持,没有更多信息的请求......没有。除此之外,只有社区相关信息。

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

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

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

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

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


1
投票

我遇到了同样的问题,这就是为我解决的问题:Stack Overflow - internal index is corrupted

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

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

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

希望这可以帮助。


1
投票

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


0
投票

你不能只使用:

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

0
投票

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

我需要在数据集DataSet中的类型表DataTable中设置新的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
}

0
投票

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

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

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

0
投票

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

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

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

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


0
投票

可能是你在同一时间在多个进程中使用相同的数据表..我只是使用qazxsw poi来解决这个问题...

试试这个..

SYNCLOCK

0
投票

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

SyncLock your datatable

'''' ----your datatable process

End SyncLock

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

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

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

10
投票

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

我不会寻找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排序匹配的排序列,这就解决了这个问题。


3
投票

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


3
投票

对于那些试图了解如何重现这个错误的人来说只是一个注释。我有一些代码会经常产生错误。它确实锁定并发读/写,但对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调用上,没有例外。


2
投票

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

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

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


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);
         }
      }
   }
}

1
投票

在这样的条件下尝试应用互斥量作为described here以在线程中引入睡眠的想法怎么样?


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


1
投票

我通过这种方式解决了我的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
© www.soinside.com 2019 - 2024. All rights reserved.