Je travaille avec une application .NET WinForms en C #, fonctionnant sous le framework 3.5 .NET. Dans cette application, je configure le membre .Expression d'une DataColumn
dans une DataTable
, comme suit:
DataColumn column = dtData.Columns["TestColumn"];
column.Expression = "some expression";
La 2ème ligne, où j'ai défini Expression
, aboutira parfois à l'exception suivante:
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)
Il n'y a pas de rime perceptible ni de raison de savoir quand l'erreur se produira; lors du chargement du même ensemble de données, cela peut fonctionner correctement, mais son rechargement échouera, et inversement. Cela me porte à penser que cela est lié à une situation de concurrence critique dans laquelle une autre opération d'écriture est en cours sur la variable DataTable
alors que j'essaie de modifier l'une de ses colonnes. Cependant, le code relatif à DataTable
s est not multi-threaded et ne s'exécute que sur le thread d'interface utilisateur.
J'ai effectué des recherches sur le Web et les forums Microsoft , et il y a beaucoup de discussions et de confusion à ce sujet. Lorsque le problème a été signalé pour la première fois en 2006, on pensait qu'il s'agissait d'une faille dans le framework .NET, et certains correctifs logiciels supposés ont probablement été intégrés dans des versions ultérieures du framework .NET. Toutefois, les utilisateurs ont signalé des résultats mitigés dans l'application de ces correctifs, qui ne sont plus applicables dans le cadre actuel.
Une autre théorie qui prévaut est qu'il existe des opérations sur le DataTable qui, bien qu'apparemment anodines, sont en réalité des opérations d'écriture. Par exemple, créer une nouvelle DataView
basée sur une DataTable
est en réalité une opération d'écriture sur la table elle-même, car il crée un index interne dans la DataTable
pour une référence ultérieure. Ces opérations d'écriture ne sont pas thread-safe, il peut donc arriver qu'une condition de concurrence conduise à une écriture non thread-safe coïncidant avec notre accès à la variable DataTable
. À son tour, cela provoque la corruption de l'index interne de la variable DataTable
, ce qui entraîne l'exception.
J'ai essayé de placer des blocs lock
autour de chaque création DataView
dans le code, mais, comme je l'ai déjà mentionné, le code utilisant la variable DataTable
n'est pas threadé, et la variable lock
s n'a aucun effet.
Quelqu'un a-t-il vu cela et a-t-il résolu/travaillé avec succès?
Non, malheureusement je ne peux pas. Le chargement du DataTable a déjà eu lieu au moment où je le récupère pour appliquer une expression à l'un de ses champs DataColumn. Je pourrais supprimer la colonne, puis l'ajouter à nouveau en utilisant le code que vous avez suggéré, mais existe-t-il une raison particulière pour laquelle cela résoudrait le problème de l'index interne?
Je viens d'avoir le même problème lors de l'importation des lignes, comme il semble, appeler DataTable.BeginLoadData
avant l'insertion corrigé pour moi.
Edit:Comme cela s’est avéré, cela ne l’a corrigé que d’un côté; maintenant, l’ajout de lignes lève cette exception.
Edit2:La suspension de la liaison suggérée par Robert Rossney a corrigé le problème de l'ajout pour moi aussi. J'ai simplement supprimé le DataSource
du DataGridView
et l'a rajouté après avoir terminé le DataTable
name__.
Edit3:Toujours pas corrigé ... l'exception continue de se glisser dans tous les endroits différents de mon code depuis jeudi ... c'est de loin le bogue le plus étrange et le plus passionnant que j'ai rencontré dans le Cadre (jusqu’à présent) (et j’ai vu beaucoup de choses étranges au cours des 3 années de travail avec .NET 2.0, suffisamment pour garantir qu’aucun de mes projets futurs ne sera construit sur ce modèle). sur le sujet.
J'ai parcouru toute la discussion sur les forums d'assistance Microsoft et je vais vous en donner un bref résumé. Le rapport de bogue d'origine provient de '05 .
Non sérieusement, cela résume à mon avis. J'ai pu extraire les informations suivantes de toute la discussion:
DataTable
est notThread-Safe. Vous devrez alors Lock
name __/Synchronize
vous-même si vous utilisez le multi-threading n'importe où.Expression
appliqué, soit un Sort
name__.DataTable.ListChanged()
; ne modifiez jamais les données de cet événement ou de tout événement qui en résulte. Cela inclut différents événements Changed
à partir de contrôles liés.DefaultView
avec un contrôle. Utilisez toujours DataTable.BeginLoadData()
et DataTable.EndLoadData()
.DefaultView
est une opération d'écrituresur le DataTable
name_et son Index
name__), le monstre volant spaghetti sait pourquoi.La source possible de ceci est probablement une condition de concurrence critique, soit dans notre code source, soit dans le code du framework. Comme il semble, Microsoft est incapable de résoudre ce bogue ou n’a aucune intention de le faire. Quoi qu'il en soit, vérifiez votre code pour les conditions de concurrence, cela a quelque chose à voir avec le DefaultView
à mon avis. À un moment donné, un Insert
ou une manipulation des données altère l'index interne, car les modifications ne sont pas correctement propagées à travers le DataTable
name__.
Je ferai bien sûr mon rapport lorsque je trouverai des informations supplémentaires ou des correctifs supplémentaires. Et désolé si je suis un peu émue ici, mais j'ai passé trois jours à essayer de cerner ce problème, et cela commence lentement à ressembler à une bonne raison de décrocher un nouvel emploi.
_ (Edit4:J'ai pu éviter ce bogue en supprimant complètement la liaison (control.DataSource = null;
) et en l'ajoutant une fois le chargement des données terminé. Ce qui alimente ma pensée qu'il a quelque chose à voir avec le DefaultView
et le événements qui apparaissent à partir des contrôles liés.
Personnellement, ce bogue particulier a été ma némésis pendant 3 semaines de différentes manières. Je l'ai résolu dans une partie de ma base de code et il apparaît ailleurs (je crois que je l'ai finalement écrasé ce soir). Les informations sur les exceptions sont plutôt inutiles, et un moyen de forcer une réindexation aurait été une fonctionnalité intéressante de Nice étant donné le manque de MS pour résoudre le problème.
Je ne chercherais pas le correctif de Microsoft - ils ont un article sur la base de connaissances, puis vous redirigent vers un correctif ASP.Net totalement indépendant.
Ok - assez se plaindre. Voyons ce qui m'a réellement aidé à résoudre ce problème particulier dans les différents endroits où je l'ai rencontré:
Cela m'a probablement le plus aidé (aussi absurde que cela puisse paraître): si une valeur ne change pas, ne l'assigne pas. Donc, dans votre cas, utilisez ceci à la place de votre affectation pure et simple:
if (column.Expression! = "une expression") column.Expression = "une expression";
(J'ai supprimé les crochets, je ne sais pas pourquoi ils étaient là).
Edit (16/05/12): Je viens de rencontrer ce problème à plusieurs reprises (avec un UltraGrid/UltraWinGrid). Utilisation de l’avis de supprimer le tri sur le DataView, puis d’ajouter une colonne triée correspondant au tri DataView, ce qui a résolu le problème.
Vous mentionnez "not threadsafe". Vous manipulez l'objet à partir de différents threads? Si tel est le cas, cela pourrait très bien être la raison de la corruption.
Juste une note pour ceux qui essaient de voir comment ce bogue peut être reproduit. J'ai un code qui produira assez souvent cette erreur. Il verrouille les lectures/écritures simultanées, mais l'appel à DataView.FindRows se fait en dehors de ce verrouillage. Le PO a souligné que créer une vue de données était une opération d'écriture masquée, l'interrogation en est-elle une aussi?
//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
Si vous l'exécutez tel quel, vous pourriez obtenir une sortie comme celle-ci (la sortie diffère quelque peu chaque fois que vous l'exécutez):
==============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 . . .
et si vous mettez le verrou sur l'appel FindRows, aucune exception.
D'après ce que j'ai compris, après un long et douloureux marchandage sur ce problème, il s'agit d'un artefact d'opérations d'écriture sans thread thread, que vous ne saviez généralement pas que vous réalisiez.
Dans mon cas, le coupable semblait être le BindingSource. J'ai constaté que je devais suspendre la liaison, effectuer l'opération que j'essayais, puis la reprendre une fois l'opération terminée, et le problème a disparu. C'était il y a 18 mois, donc je ne comprends plus les détails, mais je me souviens d'avoir eu l'impression que BindingSource effectuait une sorte d'opération sur son propre thread. (Cela a moins de sens pour moi maintenant que ce l'était à l'époque.)
L'événement RowChanging du DataTable est une autre source potentielle de problèmes. Si vous faites quelque chose qui modifie la table dans ce gestionnaire d'événements, attendez-vous à de mauvaises choses.
Pourquoi ne pas essayer d’appliquer un mutex tel que décrit ici Pour provoquer un sommeil dans le fil dans de telles conditions?
Même problème ici, et essayé une approche différente. Je n'utilise pas le datatable pour aucun truc lié à l'écran (par exemple, la liaison); Je crée simplement des objets DataRow (dans plusieurs threads) et les ajoute à la table.
J'ai essayé d'utiliser lock () et de centraliser l'ajout des lignes dans un singleton, pensant que cela aiderait. Ça n'a pas. Pour référence, voici le singleton que j'ai utilisé. Peut-être que quelqu'un d'autre pourra s'appuyer sur cela et trouver une solution?
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);
}
}
}
}
J'ai eu le même problème (index de table corrompu avec 5) lors de l'ajout de lignes par programme à un ensemble de données lié à la datagridview. Je n'ai pas pris en compte l'existence d'un gestionnaire d'événements sur l'événement AddRow de la propriété datagridview, qui effectue une initialisation au cas où l'utilisateur démarre la nouvelle ligne par l'interface utilisateur. Dans les exceptions, trace de pile, rien n’a été vu. En désactivant l’événement, je pouvais résoudre ce problème rapidement. Je n'y suis arrivé qu'en lisant quelques commentaires ici en profondeur. 2 heures pas trop pour des questions comme ça :-), je pense. Vous pouvez le trouver en définissant un point d'arrêt dans chaque gestionnaire d'événements attribué à datgridview qui est lié à l'ensemble de données.
Voici comment j'ai corrigé mon problème d'index interne est corrompu:
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;
Amusez-vous bien, Andrew M
J'ai résolu mon erreur datatable-internal-index de cette façon:
a changé CellEndEdit
en CellBeginEdit
événement. Aussi ... évitez d'utiliser des valeurs NULL inutilement:
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
J'ai eu le même problème en utilisant Threads. J'ai créé un délégué appelé lorsque j'ai besoin de fusionner la table.
internal delegate void MergeData (DataTable dataTable1, DataTable dataTable2);
internal static void MergeDataTable (DataTable dataTable1, DataTable dataTable2)
{
dataTable1.Merge (dataTable2, true);
}
Ensuite, pendant l'exécution, j'appelle le délégué et l'erreur ne se produit pas.
Delegates.MergeData mergeData = new Delegates.MergeData (Delegates.MergeDataTable);
object [] paramsMerge = {dataTable1, dataTable2};
this.Invoke (mergeData, paramsMerge);
Dans mon cas, la version du Framework est 2.0. La source d'un problème était dans l'événement DataView ListChanged. Le code ci-dessous initialise la nouvelle ligne avec certaines valeurs par défaut.
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"];
}
}
}
Après enquête, il est apparu que l'événement ItemAdded était appelé au moins deux fois par ligne. La première fois que l'interface utilisateur crée une nouvelle ligne pour la saisie de données et la deuxième fois, eh bien, je n'en suis pas sûr, mais cela ressemble à l'ajout de DataRowView à un DataView.
Le premier "if" ne fonctionne que lorsque ItemAdded est appelé pour la première fois. Au deuxième appel, la colonne "Foo" est déjà remplie et laissée telle quelle.
Cependant, le code par défaut de la colonne "Bar" peut être exécuté sur les deux appels. En fait, dans mon cas, il n'a été exécuté que lors du deuxième événement ItemAdded, lorsque l'utilisateur a eu l'occasion de renseigner les données de la colonne "Buz" (initialement, "Buz" a la valeur DBNull).
Voici donc des recommandations basées sur mes conclusions:
e.ListChangedType == ListChangedType.ItemAdded
.DBNull.Value
, etc.).La même chose m'est arrivé aussi. Winforms, .NET 3.5, a eu cette erreur inattendue en essayant de définir l’une des colonnes dans la ligne tapée. Le code était plutôt vieux et a fonctionné pendant longtemps, donc c'était un peu une surprise désagréable ...
J'avais besoin de définir de nouveaux SortNo dans la table typée TadaTable dans le jeu de données TadaSet.
Ce qui m'a aidé, vous pouvez aussi essayer ceci:
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
}
ne pouvez-vous pas simplement utiliser:
dtData.Columns.Add("TestColumn", typeof(Decimal), "Price * Quantity");
Voici ce qui semble avoir fonctionné pour ma collègue Karen et moi. Nous obtenions cette erreur dans un DataGridView, mais uniquement lors de la saisie de données dans une colonne particulière.
Il s’avère que j’ai modifié l’ordre des colonnes dans la grille, sans savoir qu’il y avait du code dans le sous-fichier DataGridView.CellValidated qui annule la valeur de la colonne dans laquelle se trouve le problème.
Ce code faisait référence à un numéro de colonne spécifique. Ainsi, lorsque la colonne DataGridView 3 d'origine a été déplacée et est devenue la colonne 1, mais que le code DataGridView.CellValidated faisait toujours référence à la colonne 3, l'erreur s'est produite. Changer notre code pour qu'il fasse référence au bon e.ColumnIndex semble avoir résolu notre problème.
(Il n'a pas été facile de comprendre comment changer ce numéro dans notre code. J'espère que ce correctif est valable.)
Peut-être que vous utilisez le même datatable dans plusieurs processus en même temps .. Je viens de résoudre ce problème en utilisant SYNCLOCK
...
Essaye ça..
SyncLock your datatable
'''' ----your datatable process
End SyncLock