DataTable内部索引已损坏

时间:2009-01-16 15:09:37

标签: c# .net datatable

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

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

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

我已尝试在代码中的每个lock创建周围放置DataView块,但正如我之前提到的,使用DataTable的代码没有线程化,{{1}在任何情况下都无效。

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


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

18 个答案:

答案 0 :(得分:20)

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

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

Edit2:按照Robert Rossney的建议暂停绑定也为我解决了添加问题。我只是从DataSource中删除了DataGridView,并在完成DataTable后对其进行了补充。

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

我已经阅读了整个讨论at the Microsoft support forums,我将简要介绍一下。{3}}原bug report originates in '05

  • 2006年3月: 第一次报告错误,调查开始。在整个明年的过程中,它以不同的形式和不同的表现形式报道。
  • 2007年3月: 最后a hotfix with number KB 932491已发布(请不要抱怨),它会链接到a download of an completely irrelevant looking hotfix,或者似乎至少如此。在接下来的几个月中,许多人报告修补程序不起作用,有些报告成功。
  • 2007年7月: 微软的最后一次现场直播(完整无用的答案),超出这一点,微软没有进一步的回应。没有进一步的确认,没有尝试支持,没有更多信息的请求......没有。除此之外,只有社区相关信息。

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

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

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

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

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

答案 1 :(得分: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排序匹配的排序列,这解决了这个问题。

答案 2 :(得分:3)

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

答案 3 :(得分: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调用上,没有例外。

答案 4 :(得分:2)

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

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

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

答案 5 :(得分: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);
         }
      }
   }
}

答案 6 :(得分:1)

如何尝试将互斥量应用为described here 在这种情况下诱导线程睡眠?

答案 7 :(得分: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

答案 8 :(得分:1)

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

答案 9 :(得分: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 方法中,我在线程中进行了大量计算。

希望这有帮助。

答案 10 :(得分: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

答案 11 :(得分:0)

.NET 4.5.2 Win Forms应用程序具有相同的功能。 就我而言,原因似乎是绑定到单个BindingSource列的多个控件。我知道将许多控件绑定到一个值会带来麻烦,但是由于布局非常复杂,对我来说,这并不是一件容易的事。

似乎从一侧更改该值会引发多个OnChange事件,这些事件试图对BindingSource执行相同的操作,从而导致相关错误。

我尝试暂停绑定,但是没有成功。我已经实现了一些标志,可以防止代码并行执行多次,并且情况会更好。

答案 12 :(得分:0)

你不能只使用:

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

答案 13 :(得分:0)

我使用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);

答案 14 :(得分:0)

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

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

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

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

答案 15 :(得分: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值)。

所以这是根据我的发现提出的建议:

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

答案 16 :(得分:0)

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

试试这个..

SyncLock your datatable

'''' ----your datatable process

End SyncLock

答案 17 :(得分: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
}