3.5 .NETフレームワークに対して実行しているC#で.NET WinFormsアプリを使用しています。このアプリでは、DataColumn
のDataTable
の.Expressionメンバーを次のように設定しています。
DataColumn column = dtData.Columns["TestColumn"];
column.Expression = "some expression";
2行目で実際に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)
エラーがいつ発生するかについて、知覚できる韻や理由はありません。同じデータセットをロードする場合、正常に動作する可能性がありますが、再ロードは失敗し、その逆も同様です。これは、列の1つを変更しようとしているときにDataTable
で別の書き込み操作が発生している競合状態に関連していると思います。ただし、DataTable
sに関連するコードはではなくマルチスレッドであり、UIスレッドでのみ実行されます。
私はウェブと Microsoftフォーラム を検索しましたが、この問題については多くの議論と混乱があります。 2006年にこの問題が最初に報告されたとき、それは.NETフレームワークの欠陥であると考えられていました。ただし、現在のフレームワークには適用されなくなった修正プログラムを適用した結果は、さまざまです。
別の一般的な理論は、一見無害であるように見えますが、実際には書き込み操作であるDataTableに対する操作があるということです。たとえば、DataView
に基づいて新しいDataTable
を作成することは、後で参照するためにDataTable
に内部インデックスを作成するため、実際にはテーブル自体に対する書き込み操作です。これらの書き込み操作はスレッドセーフではないため、競合状態により、DataTable
へのアクセスと一致しないスレッドセーフな書き込みが発生することがあります。これにより、DataTable
の内部インデックスが破損し、例外が発生します。
lock
ブロックをコード内の各DataView
作成の周りに配置しようとしましたが、前に述べたように、DataTable
を利用するコードはスレッド化されておらず、lock
sは、いずれの場合も効果がありませんでした。
誰かがこれを見て、うまく解決/回避しましたか?
いいえ、残念ながらできません。 DataTableの読み込みは、そのDataColumnの1つに式を適用するためにそれを手に入れるまでに既に行われています。列を削除してから、提案されたコードを使用して列を再度追加することもできますが、それによって内部インデックスが破損する問題を解決する特別な理由はありますか?
行のインポート中に同じ問題が発生したようですが、挿入によって修正される前に_DataTable.BeginLoadData
_を呼び出したようです。
編集:結局のところ、これは片側だけを修正しましたが、行を追加するとこの例外がスローされます。
Edit2:Robert Rossney によって提案されたバインディングの一時停止により、追加の問題も修正されました。私は単にDataSource
をDataGridView
から削除し、DataTable
を使い終わった後でそれを追加しました。
Edit3:まだ修正されていません...木曜日以降、例外がコードのすべての場所で忍び寄っています...これははるかに奇妙で、私がこれまでにフレームワークで遭遇した最もf ****なバグ(そして、.NET 2.0で作業していた3年間で奇妙なことをたくさん見てきましたが、将来の1つではないことを保証するのに十分プロジェクトはその上に構築されます)。しかし、話題に戻って、十分な怒りの言葉です。
私はすべてのディスカッションを読み終えました Microsoftサポートフォーラムで とそれについて簡単に要約します。元の バグレポートは'05年に作成されました 。
真剣に、これは私の意見でそれを要約しません。ディスカッション全体から次の情報を抽出することができました。
DataTable
はnotスレッドセーフです。マルチスレッディングがどこかにある場合は、自分でLock
/Synchronize
する必要があります。Expression
または適用されたSort
です。DataTable.ListChanged()
イベントです。このイベントまたはそれから発生するイベントのデータを変更しないでください。これには、バインドされたコントロールからのさまざまなChanged
イベントが含まれます。DefaultView
をバインドするときに問題が発生する可能性があります。常にDataTable.BeginLoadData()
およびDataTable.EndLoadData()
を使用してください。DefaultView
の作成と操作は、DataTable
(およびそのIndex
に対する書き込み操作です。 )、フライングスパゲッティモンスターはその理由を知っています。この原因として考えられるのは、おそらくソースコードまたはフレームワークのコードのいずれかにおける競合状態です。どうやら、マイクロソフトはこのバグを修正することができないか、修正するつもりはありません。いずれにせよ、コードの競合状態を確認してください。私の意見では、それはDefaultView
と関係があります。ある時点で、変更がInsert
全体に適切に伝播されないため、DataTable
またはデータの操作によって内部インデックスが破損しています。
もちろん、さらに情報や追加の修正が見つかった場合は報告します。ここで少し感情的になった場合は申し訳ありませんが、この問題を特定するために3日間費やしてきましたが、ゆっくりと新しい仕事に就く正当な理由に見え始めています。
Edit4:バインディング(_control.DataSource = null;
_)を完全に削除し、データのロード後に再度追加することで、このバグを回避できました完成されました。これは、バインドされたコントロールから発生するDefaultView
およびイベントと関係があるという私の考えを刺激します。
個人的に、この特定のバグは、さまざまな方法で3週間私の宿敵となっています。私はコードベースの一部でそれを解決しましたが、他の場所で表示されます(今夜、ようやく潰したと思います)。例外情報はあまり役に立たず、再インデックスを強制する方法は、問題を解決するためのMSの欠如を考えると素晴らしい機能でした。
私はMSのホットフィックスを探すつもりはありません-それらにはKB記事があり、それから完全に無関係なASP.Netフィックスにリダイレクトします。
わかりました-十分不満です。私が遭遇したさまざまな場所でこの特定の問題を解決するのに実際に私が役立ったものを見てみましょう。
これはおそらく私に最も役立ちました(不思議なことに不合理です)。値が変化しない場合は、割り当てないでください。したがって、yourの場合は、完全な割り当ての代わりにこれを使用します。
if(column.Expression!= "some expression")column.Expression = "some expression";
(角括弧を削除しましたが、なぜそこにあったのかはわかりません)。
編集(5/16/12):この問題に繰り返し遭遇しました(UltraGrid/UltraWinGridを使用)。 DataViewの並べ替えを削除するアドバイスを使用し、DataViewの並べ替えと一致する並べ替え済みの列を追加しました。これにより問題が解決しました。
あなたは「スレッドセーフではない」と述べています。異なるスレッドからオブジェクトを操作していますか?もしそうなら、それは非常によく破損の理由かもしれません。
このバグを再現する方法を確認しようとしている人へのメモです。私はかなり頻繁にそのエラーを生成するいくつかのコードを持っています。同時の読み取り/書き込みをロックしますが、DataView.FindRowsの呼び出しはそのロックの外側で行われます。 OPは、creatingデータビューは非表示の書き込み操作であると指摘しました。
//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);
}
}
}
}
私は同じ問題に遭遇し、これが私のためにそれを修正したものです Stack Overflow-内部インデックスが破損しています 。
データセットでスレッドを使用している場合、そのエラーが発生します。
私の場合、スレッドで実行されているメソッド内でデータセットの新しい行を作成しようとしました。
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メソッドでは、スレッドで多くの計算を行っています。
お役に立てれば。
Datagridviewにバインドされているデータセットに行をプログラムで追加すると、同じ問題(テーブルインデックスが5で破損する)が発生しました。 datagridviewのAddRowイベントにイベントハンドラーがあり、ユーザーがUIで新しい行を開始した場合に初期化を行うことを考慮していません。例外スタックトレースでは、何も見られませんでした。イベントを無効にすることで、これをすばやく解決できました。私はここでいくつかのコメントを深く読んだだけでそれに到達しました。そのような問題については2時間はあまり多くありません:-)、私は思います。これは、データセットにリンクされているdatgridviewに割り当てられているすべてのイベントハンドラーにブレークポイントを設定することで確認できます。
これは、内部インデックスが破損している問題を修正する方法です。
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;
楽しんで、Andrew 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
.NET 4.5.2勝利フォームアプリと同じでした。私の場合、理由は、単一のBindingSource列にバインドされた複数のコントロールにあるように見えました。多くのコントロールを1つの値にバインドすると問題が発生することは承知していますが、レイアウトがかなり複雑なため、私にとってはそれほど問題ではありませんでした。
片側から値を変更すると、問題のエラーが発生したBindingSourceに対して同じ操作を実行しようとする複数のOnChangeイベントが発生したようです。
バインドを一時停止しようとしましたが、成功しませんでした。フラグを実装して、コードが並列で何度も実行されるのを防ぎました。
スレッドを使用して同じ問題が発生しました。私がしたことは、テーブルをマージする必要があるときに呼び出されるデリゲートを作成することでした。
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);
私の場合、フレームワークのバージョンは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イベントが少なくとも1行に2回呼び出されることが明らかになりました。最初にUIがデータを入力するための新しい行を作成するとき、2回目はよくわかりませんが、DataRowViewがDataViewに追加されるときのように見えます。
最初の「if」は、ItemAddedが初めて呼び出されたときにのみ機能します。 2回目の呼び出しでは、「Foo」列はすでに入力されており、そのままです。
ただし、「Bar」列のデフォルトコードは、両方の呼び出しで実行できます。実際、私の場合は、ユーザーが「Buz」列のデータを入力する機会があったときに、2番目のItemAddedイベントでのみ実行されました(最初は「Buz」にはDBNull値があります)。
だからここに私の発見に基づく推奨事項があります:
e.ListChangedType == ListChangedType.ItemAdded
。DBNull.Value
など)同じことが私にも起こりました。 Winforms(.NET 3.5)は、型付きの行に列の1つを設定しようとすると、予期せずこのエラーが発生しました。コードはかなり古く、長い間動作していたので、ちょっと不愉快な驚きでした...
データセット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
}
あなたはただ使うことはできません:
dtData.Columns.Add("TestColumn", typeof(Decimal), "Price * Quantity");
これが私の同僚のカレンと私にとってうまくいったようです。DataGridViewでこのエラーが発生しましたが、特定の1つの列にデータを入力したときのみです。
グリッド内の列の順序を変更したことがわかりましたが、DataGridView.CellValidatedサブルーチンに、特定の1つの列の値をnullにするコードがあったため、問題が発生したことがわかりません。
そのコードは特定の列番号を参照していました。したがって、元のDataGridView列3が移動されて列1になったが、DataGridView.CellValidatedコードがまだ列3を参照していた場合、エラーが発生しました。正しいe.ColumnIndexを参照するようにコードを変更すると、問題が解決したようです。
(コードでこの1つの番号を変更するのは簡単ではありませんでした。この修正が当てはまることを願っています。)
同時に複数のプロセスで同じデータテーブルを使用している可能性があります。SYNCLOCK
...を使用してこの問題を解決しました.
これを試して..
SyncLock your datatable
'''' ----your datatable process
End SyncLock