it-swarm.com.ru

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

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

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

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

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

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


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

26
user30525

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

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

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

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

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

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

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

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

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

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

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

20
Bobby

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

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

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

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

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

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

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

10
torial

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

3
Lasse Vågsæther Karlsen

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

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

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

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

2
Robert Rossney

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

1
Suj

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

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

1
rgaab

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

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; 

Наслаждайтесь, Андрей М

1
Andrew Marais

Я решил свою ошибку 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
Samuel Darteh

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

В моем случае версия 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. 

Первое «если» работает только тогда, когда ItemAdded вызывается в первый раз. При втором вызове столбец «Foo» уже заполнен и оставлен как есть.

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

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

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

То же самое случилось со мной тоже. 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
}
0
Schnapz

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

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

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

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

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

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

0
Mike Banach

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

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

SyncLock your datatable

'''' ----your datatable process

End SyncLock
0
URVISH SUTHAR