web-dev-qa-db-fra.com

L'index interne de DataTable est corrompu

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 à DataTables 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 locks 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?

26
user30525

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 DataSourcedu DataGridViewet l'a rajouté après avoir terminé le DataTablename__.

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 .

  • Mars '06:} _Un bogue est signalé pour la première fois, une enquête est ouverte. Tout au long de l'année prochaine, il est signalé sous différentes formes et différentes manifestations.
  • Mars '07:} _Enfin un correctif portant le numéro KB 932491 est publié (n'ayez pas votre espoir), il crée un lien contre le téléchargement d'un correctif à la recherche totalement non pertinent. , ou du moins semble-t-il. Au cours des prochains mois, de nombreuses personnes signalent que le correctif le correctif ne fonctionne pas, certains signalent un succès.
  • Juillet '07:} _Dernier signe de direct de Microsoft (avec une réponse totalement inutile), au-delà de ce point, il n'y a plus de réponse de Microsoft. Aucune autre confirmation, aucune tentative de support, aucune demande de plus d'informations ... rien Au-delà de ce point, il n'y a que des informations relatives à la communauté.

Non sérieusement, cela résume à mon avis. J'ai pu extraire les informations suivantes de toute la discussion:

  • DataTableest notThread-Safe. Vous devrez alors Lockname __/Synchronizevous-même si vous utilisez le multi-threading n'importe où.
  • La corruption de l'index se passe quelque part avantl'exception réelle est levée.
  • Une source de corruption possible est soit un Expressionappliqué, soit un Sortname__.
  • Une autre source possible est l’événement 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.
  • Il existe des problèmes possibles lors de la liaison de DefaultViewavec un contrôle. Utilisez toujours DataTable.BeginLoadData() et DataTable.EndLoadData().
  • La création et la manipulation de DefaultViewest une opération d'écrituresur le DataTablename_et son Indexname__), 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 Insertou une manipulation des données altère l'index interne, car les modifications ne sont pas correctement propagées à travers le DataTablename__.

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 DefaultViewet le événements qui apparaissent à partir des contrôles liés.

20
Bobby

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é:

  • Évitez d’utiliser des vues par défaut et de modifier la vue par défaut si possible. Btw, .Net 2.0 a un certain nombre de verrous de lecteur/écrivain sur la création de vues, ils ne sont donc pas le problème qu’ils étaient antérieurs à 2.0.
  • Appelez AcceptChanges () si possible. 
  • Faites attention à .Select (expression), car il n'y a pas de lecteur/écrivain verrouillé dans ce code - et c'est le seul endroit (du moins selon une personne sur le réseau, donc prenez-le avec un grain de sel - cependant , cela ressemble beaucoup à votre problème - l’utilisation de mutex peut donc aider) 
  • Définissez AllowDBNull sur la colonne en question (valeur discutable, mais signalée sur le usenet - je ne l'ai utilisée que dans les endroits où cela fait sens)
  • Assurez-vous de ne pas définir null (C #)/Nothing (VB) sur un champ DataRow. Utilisez DBNull.Value au lieu de null. Dans votre cas, vous voudrez peut-être vérifier que le champ n'est pas nul, la syntaxe d'expression does prend en charge l'opérateur IsNull (val, alt_val).
  • 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.

10
torial

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. 

3
Anssssss

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.

2
Robert Rossney

Pourquoi ne pas essayer d’appliquer un mutex tel que décrit ici Pour provoquer un sommeil dans le fil dans de telles conditions?

1
Suj

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);
         }
      }
   }
}
1
David Catriel

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.

1
rgaab

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

1
Andrew Marais

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
1
Samuel Darteh

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);
0
Ailton

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:

  • Les données de l'événement ListChanged ne peuvent être modifiées que lorsque e.ListChangedType == ListChangedType.ItemAdded.
  • Avant de définir la valeur de la colonne, vous devez vérifier si il s’agit du premier événement ItemAdded (par exemple, si la valeur ne peut pas être nulle lors du deuxième appel, vérifiez si c’est DBNull.Value, etc.).
0
Artemix

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
}
0
Schnapz

ne pouvez-vous pas simplement utiliser:

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

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.)

0
Mike Banach

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
0
URVISH SUTHAR