Подтвердить что ты не робот

Внутренний индекс DataTable поврежден

Я работаю с приложением .NET WinForms на С#, работающим с 3.5.NET framework. В этом приложении я устанавливаю элемент .Expression DataColumn в DataTable, например:

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, не является многопоточным и работает только в потоке пользовательского интерфейса.

Я искал в Интернете и форумы Microsoft, и есть много дискуссий и путаницы по этой проблеме. Назад, когда проблема впервые появилась в 2006 году, была мысль о том, что это был недостаток в платформе .NET, и были выпущены некоторые предполагаемые исправления, которые предположительно были перенесены в более поздние версии платформы .NET. Тем не менее, люди сообщают о смешанных результатах применения этих исправлений, которые больше не применимы к текущей структуре.

Другая распространенная теория заключается в том, что в DataTable есть операции, которые, хотя кажутся безобидными, на самом деле являются операциями записи. Например, создание новой DataView на основе DataTable на самом деле является операцией записи в самой таблице, поскольку она создает внутренний индекс в DataTable для последующей ссылки. Эти операции записи не являются потокобезопасными, поэтому иногда случается, что условие гонки приводит к записи без прочтения, совпадающей с нашим доступом к DataTable. Это, в свою очередь, приводит к повреждению внутреннего индекса DataTable, что приводит к исключению.

Я попытался помещать lock блоки вокруг каждого создания DataView в коде, но, как я уже упоминал ранее, код, использующий DataTable, не имеет резьбы, а lock не имеет никакого эффекта, в любом случае.

Кто-нибудь видел это и успешно решил/работал вокруг него?


Нет, к сожалению, я не могу. Загрузка DataTable уже произошла к тому моменту, когда я удержал его, чтобы применить выражение к одному из его DataColumn. Я мог бы удалить столбец, а затем снова добавить его с помощью кода, который вы предложили, но есть ли какая-то особая причина, почему это может решить внутренний индекс - это проблема с повреждением?

4b9b3361

Ответ 1

У меня просто была такая же проблема при импорте строк, как кажется, при вызове DataTable.BeginLoadData до того, как вставка исправила это для меня.

Изменить: Как оказалось, это только исправило это на одной стороне, теперь добавление строк вызывает это исключение.

Edit2: приостановка связывания, как это было предложено Робертом Россни, также исправила проблему добавления. Я просто удалил DataSource из DataGridView и прочитал его после того, как закончил работу с DataTable.

Edit3: все еще не исправлено... исключение продолжает появляться во всех разных местах в моем коде с четверга... это самая странная и самая ужасная ошибка, с которой я столкнулся в Framework до сих пор ( и я видел много странных вещей за 3 года, что я работал с .NET 2.0, достаточно, чтобы гарантировать, что ни один из моих будущих проектов не будет построен на нем). Но хватит разглагольствовать, вернемся к теме.

Я прочитал всю дискуссию на форумах поддержки Microsoft и кратко изложу ее. Оригинальный отчет об ошибках возник в '05.

  • Март '06: ошибка сообщается в первый раз, начинается расследование. В течение следующего года он сообщается в разных формах и разных проявлениях.
  • Март '07: наконец- то выпущено исправление с номером KB 932491 (не надейтесь на это), оно ссылается на загрузку совершенно неактуального на вид исправления или, по крайней мере, так кажется. В течение следующих месяцев многие сообщают, что исправление не работает, некоторые сообщают об успехе.
  • Июль '07: последний признак живого от Microsoft (с полным бесполезным ответом), кроме этого, больше нет ответа от Microsoft. Никаких дальнейших подтверждений, никаких попыток поддержки, никаких запросов на дополнительную информацию... ничего. Помимо этого, есть только информация, связанная с сообществом.

Нет, серьезно, это подводит итог, по моему мнению. Мне удалось извлечь следующую информацию из всего обсуждения:

  • DataTable не является потокобезопасным. Вам придется Lock/Synchronize его самостоятельно, если на нем есть многопоточность.
  • Повреждение индекса происходит где-то до того, как выдается фактическое исключение.
  • Одним из возможных источников повреждения является либо примененное Expression либо примененная Sort.
  • Другим возможным источником является событие DataTable.ListChanged(), никогда не изменяйте данные в этом событии или любое событие, которое возникает из него. Это включает в себя различные Changed события от связанных элементов управления.
  • Существуют возможные проблемы при привязке DefaultView к DefaultView управления. Всегда используйте DataTable.BeginLoadData() и DataTable.EndLoadData().
  • Создание и манипулирование DefaultView - это операция записи в DataTable (и его Index), который знает почему Flying Spaghetti Monster.

Возможный источник этого, скорее всего, является условием гонки, либо в нашем исходном коде, либо в коде платформы. Похоже, что Microsoft не может исправить эту ошибку или не собирается этого делать. В любом случае, проверьте свой код на наличие условий гонки, это как-то связано с DefaultView по моему мнению. В какой-то момент Insert или манипулирование данными повреждают внутренний индекс, потому что изменения не распространяются должным образом по всей DataTable.

Я, конечно, сообщу, когда найду дополнительную информацию или дополнительные исправления. И извините, если я здесь немного взволнован, но я потратил три дня, пытаясь выявить эту проблему, и она постепенно начинает казаться хорошей причиной, чтобы получить новую работу.

Edit4: мне удалось избежать этой ошибки, полностью удалив привязку (control.DataSource = null;) и повторно добавив ее после завершения загрузки данных. Который подпитывает мою мысль, что это как-то связано с DefaultView и событиями, которые появляются из связанных элементов управления.

Ответ 2

Лично эта особая ошибка была моей Немезидой в течение 3 недель в разных моделях. Я решил это в одной части моей базы кода, и она появляется в другом месте (я думаю, что я, наконец, раздавил ее сегодня). Информация об исключении довольно бесполезна, и способ заставить reindex был бы приятной особенностью, учитывая отсутствие MS для решения проблемы.

Я бы не искал MS-исправление - на нем есть статья в KB, а затем перенаправляет вас на исправление ASP.Net, которое полностью не связано.

Хорошо - достаточно жаловаться. Посмотрим, что на самом деле помогло мне в решении этой конкретной проблемы в разных местах, где я столкнулся с ней:

  • Избегайте использования представлений по умолчанию и, если возможно, изменяйте представление по умолчанию. Btw,.Net 2.0 имеет ряд блокировок чтения/записи при создании представлений, поэтому они не являются проблемой, которой они были до 2.0.
  • Вызовите AcceptChanges(), где это возможно.
  • Будьте осторожны. Выберите (выражение), поскольку в этом коде нет блокировки чтения/записи - и это единственное место (по крайней мере, по словам человека на usenet, так что возьмите его с солью - однако это очень похоже на вашу проблему, поэтому использование мутексов может помочь).
  • Установите AllowDBNull в соответствующий столбец (сомнительное значение, но сообщается в usenet - я использовал его только в тех местах, где это имеет смысл)
  • Убедитесь, что вы не устанавливаете значение null (С#)/Nothing (VB) в поле DataRow. Используйте DBNull.Value вместо null. В вашем случае вы можете проверить, что поле не равно null, синтаксис выражения поддерживает оператор IsNull (val, alt_val).
  • Это, вероятно, помогло мне (абсурдно, как это звучит): если значение не меняется, не назначайте его. Поэтому в вашем случае используйте это вместо своего прямого назначения:

    if (column.Expression!= "some expression" ) column.Expression = "some expression";

(Я удалил квадратные скобки, не уверен, почему они были там).

Изменить (5/16/12): Просто наткнулся на эту проблему повторно (с помощью UltraGrid/UltraWinGrid). Использовал совет по удалению сортировки в DataView, а затем добавил отсортированный столбец, который соответствовал сортировке DataView, и это устранило проблему.

Ответ 3

Вы упомянули "не потоковые". Вы манипулируете объектом из разных потоков? Если это так, то это может быть причиной коррупции.

Ответ 4

Просто примечание для тех, кто пытается понять, как эту ошибку можно воспроизвести. У меня есть код, который довольно часто приводит к этой ошибке. Он блокирует одновременное чтение/запись, но вызов DataView.FindRows выполняется за пределами этой блокировки. ОП указал, что создание представления данных было скрытой операцией записи, также запрашивает ее?

//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 there, delete it
                    {
                        row.Delete();
                        Employees.AcceptChanges();
                    }
                    else //it 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, никаких исключений.

Ответ 5

Мое понимание, из-за долгой и мучительной торговли этой проблемой, заключается в том, что это артефакт безпоточных операций записи, который обычно вы не знали, что делаете.

В моем случае виновник, похоже, был BindingSource. Я обнаружил, что мне нужно приостановить привязку, выполнить любую операцию, которую я пытался, а затем возобновить привязку, когда я закончил, и проблема исчезла. Это было 18 месяцев назад, поэтому я больше не разбираюсь в деталях, но помню, как создается впечатление, что BindingSource выполняет какую-то операцию в своем потоке. (Это делает мне меньше смысла сейчас, чем в то время.)

Другим потенциальным источником проблем является событие DataTable RowChanging. Если вы делаете что-то, что изменяет таблицу в обработчике событий, ожидайте плохие вещи.

Ответ 6

То же самое и здесь, и попробовал другой подход. Я не использую datatable для любого связанного с экраном материала (например, привязки); Я просто создаю объекты 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);
         }
      }
   }
}

Ответ 7

Как пробовать идею применения мьютекса как описанный здесь вызывать сон в нитке в таких условиях?

Ответ 8

Вот как я исправил свой внутренний индекс, это проблема с повреждением:

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; 

Enjoy, Эндрю М

Ответ 9

Я разрешил эту ошибку с внутренним индексом:

изменено 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

Ответ 10

Я столкнулся с той же проблемой, и это исправило это для меня: Qaru - внутренний индекс поврежден.

Если вы используете потоки с набором данных, эта ошибка возникнет.

В моем случае я пытался создать новую строку для набора данных в методе, который выполнялся в потоках.

Один из способов заключался в использовании 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 я делаю много вычислений в потоках.

Надеюсь, что это поможет.

Ответ 11

У меня была та же проблема (индекс таблицы, поврежденный с 5), при добавлении строк программно в набор данных, привязанный к datagridview. Я не принимал во внимание, что в событии AddRow в datagridview был обработчик событий, который выполняет некоторую инициализацию в случае, если пользователь запускает новую строку с помощью пользовательского интерфейса. В следах стека исключений ничего не было видно. Отключив событие, я смог быстро решить эту проблему. Я пришел к нему, только прочитав некоторые комментарии здесь глубоко. 2 часа не слишком много для таких вопросов:-), я думаю. Вы можете найти это, установив точку останова в каждом обработчике событий, назначенном datgridview, который связан с набором данных.

Ответ 12

Вы не можете просто использовать:

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

Ответ 13

То же самое со мной произошло. Winforms,.NET 3.5, не удалось получить эту ошибку, пытаясь установить один из столбцов в типизированной строке. Код был довольно старым и долгое время работал, поэтому это было неприятное удивление...

Мне нужно было установить новый SortNo в типизированной таблице TadaTable в наборе данных TadaSet.

Что мне помогло, вы также можете попробовать следующее:

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
}

Ответ 14

Возможно, вы используете одинаковые данные в процессе mutiple в то же время. Я просто решил эти проблемы, используя SYNCLOCK...

Попробуйте это.

SyncLock your datatable

'''' ----your datatable process

End SyncLock

Ответ 15

В моем случае версия 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 вызывается не реже двух раз подряд. В первый раз, когда пользовательский интерфейс создает новую строку для ввода данных и второй раз, я не уверен, но выглядит, когда DataRowView добавлен в DataView.

Первое "if" работает только при первом вызове ItemAdded. На втором вызове столбец "Foo" уже заполнен и оставлен как есть.

Однако код разблокировки столбца "Бар" может выполняться при обоих вызовах. Фактически, в моем случае он выполнялся только во втором событии ItemAdded, когда у пользователя была возможность заполнить данные для столбца "Buz" (изначально "Buz" имеет значение DBNull).

Итак, вот рекомендации, основанные на моих выводах:

  • Данные в событии ListChanged могут быть изменены только тогда, когда e.ListChangedType == ListChangedType.ItemAdded.
  • Перед установкой значения столбца необходимо выполнить проверку, чтобы убедиться, что это первое событие ItemAdded (например, если значение не может быть равно нулю при втором вызове, проверьте, есть ли оно DBNull.Value и т.д.).

Ответ 16

Вот что, похоже, сработало для моего коллеги Карена и Я. Мы получили эту ошибку в DataGridView, но только при вводе данных в один конкретный столбец.

Оказывается, я изменил порядок столбцов в сетке, не зная, что в элементе DataGridView.CellValidated есть код, который обнуляет значение в этом конкретном столбце, вызывающем проблему.

Этот код относится к определенному номеру столбца. Поэтому, когда исходный столбец 3 DataGridView был перемещен и стал столбцом 1, но код DataGridView.CellValidated все еще упоминается в столбце 3, произошла ошибка. Изменив наш код так, чтобы он ссылался на правильный e.ColumnIndex, похоже, исправили нашу проблему.

(Нелегко было понять, как изменить этот номер в нашем коде. Надеюсь, это исправление сохраняется.)

Ответ 17

У меня была такая же проблема с использованием 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);

Ответ 18

То же самое было и с .NET 4.5.2 win win app. В моём случае причина оказалась в нескольких элементах управления, привязанных к одному столбцу BindingSource. Я знаю, что привязка многих элементов управления к одному значению вызывает проблемы, но я был меньшим злом для меня из-за довольно сложной компоновки.

Оказалось, что изменение значения с одной стороны вызвало несколько событий OnChange, пытающихся выполнить одну и ту же операцию с BindingSource, что привело к указанной ошибке.

Я пытался приостановить привязку, но безуспешно. Я реализовал флаги, предотвращающие многократное выполнение кода параллельно, и все стало лучше.