我正在使用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
的代码没有线程,并且lock
s无论如何都没有效果。
有没有人看过这个并成功解决/解决它?
不,不幸的是我做不到。加载DataTable已经发生,当我抓住它以将Expression应用于其中一个DataColumn时。我可以删除列,然后使用您建议的代码重新添加它,但有一个特殊的原因,为什么这将解决内部索引已损坏的问题?
我在导入行时遇到了同样的问题,因为看起来,在插入修复它之前调用DataTable.BeginLoadData
。
编辑:事实证明,这只是在一侧修复它,现在添加行会抛出此异常。
编辑2:Robert Rossney建议的暂停绑定也为我修复了添加问题。我简单地从DataSource
中删除了DataGridView
,并在完成DataTable
后对其进行了重新读取。
编辑3:仍然没有修复...自从星期四以来,我的代码中的异常一直在爬行...这是到目前为止我在框架中遇到的最奇怪和最常见的错误(在我使用.NET 2.0的3年里,我看到了许多奇怪的事情,足以保证我未来的项目不会在其上构建。)但足够的咆哮,回到主题。
我已经阅读了整个讨论at the Microsoft support forums,我会给你一个简短的总结。原来的bug report originates in '05。
不认真,这在我看来总结了。我能够从整个讨论中提取以下信息:
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
以及从绑定控件中产生的事件有关。
我遇到了同样的问题,这就是为我解决的问题: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方法中,我在线程中进行了大量的计算。
希望这可以帮助。
在以编程方式向行绑定到datagridview的数据集添加行时,我遇到了同样的问题(表索引已损坏为5)。我没有考虑到datagridview的AddRow事件上有一个事件处理程序,如果用户通过UI启动新行,它会进行一些初始化。在异常堆栈跟踪中没有看到它。通过禁用该事件,我可以快速解决这个问题。我只是通过深入阅读一些评论来实现它。对于这样的问题,2小时不是太多:-),我想。您可以通过在分配给与数据集链接的datgridview的每个事件处理程序中设置断点来找到它。
你不能只使用:
dtData.Columns.Add("TestColumn", typeof(Decimal), "Price * Quantity");
同样的事也发生在我身上。 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
}
在我的例子中,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
时更改。DBNull.Value
等)这似乎对我的同事Karen和我有用。我们在DataGridView中收到此错误,但仅在将数据输入到一个特定列时。
事实证明,我已经更改了网格中列的顺序,而不知道DataGridView.CellValidated子中的代码是否会导致该特定列中的值导致问题。
该代码引用了特定的列号。因此,当原始DataGridView列3被移动并成为第1列,但DataGridView.CellValidated代码仍然引用第3列时,发生了错误。更改我们的代码,以便它引用正确的e.ColumnIndex似乎已修复我们的问题。
(要想在我们的代码中更改这一个数字并不容易。我希望这个解决方案成立。)
可能是你在同一时间在多个进程中使用相同的数据表..我只是使用qazxsw poi来解决这个问题...
试试这个..
SYNCLOCK
我使用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);
}
就个人而言,这个特殊的错误已成为我各种时尚的3周克星。我已经在我的代码库的一部分解决了它,它出现在其他地方(我相信我终于在今晚压制它)。异常信息相当无益,并且由于缺少MS来解决问题,强制重新索引的方法将是一个很好的功能。
我不会寻找MS的修补程序 - 他们有一篇知识库文章,然后将你重定向到一个完全不相关的ASP.Net修复程序。
好的 - 足够的抱怨。让我们看看在我遇到过的各个地方解决这个问题的实际帮助我:
(我删除了方括号,不知道为什么他们在那里)。
编辑(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<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调用上,没有例外。
我对这个问题的长期和痛苦的讨价还价的理解是,它是非线程安全写操作的工件,通常你不知道自己在做什么。
就我而言,罪魁祸首似乎是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);
}
}
}
}
在这样的条件下尝试应用互斥量作为described here以在线程中引入睡眠的想法怎么样?
这是我修复内部索引是如何损坏的问题:
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