Внутренний индекс DataTable поврежден - PullRequest
27 голосов
/ 16 января 2009

Я работаю с приложением .NET WinForms в C #, работающем на платформе 3.5 .NET. В этом приложении я устанавливаю член .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 s, является , а не многопоточным и выполняется только в потоке пользовательского интерфейса.

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

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

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

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


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

Ответы [ 17 ]

20 голосов
/ 14 апреля 2011

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

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

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

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

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

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

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

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

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

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

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

10 голосов
/ 29 января 2009

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

Я бы не стал искать исправления MS - у них есть статья в КБ, а затем перенаправил вас на исправление ASP.Net, которое совершенно не связано.

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

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

    if (column.Expression! = "Некоторое выражение") column.Expression = "some expression";

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

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

3 голосов
/ 14 августа 2012

Просто примечание для тех, кто пытается увидеть, как эту ошибку можно воспроизвести. У меня есть некоторый код, который довольно часто выдает эту ошибку. Он блокирует параллельное чтение / запись, но вызов 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'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, исключений нет.

3 голосов
/ 17 января 2009

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

2 голосов
/ 19 марта 2009

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

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

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

1 голос
/ 08 декабря 2015

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

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

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

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

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

1 голос
/ 02 декабря 2015

Я решил свою ошибку 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
1 голос
/ 03 июня 2011

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

1 голос
/ 19 мая 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...