2016-08-24 7 views
1

В следующем коде, у меня есть выражение LINQ, который производит EnumerableRowCollection(Of DataRow), что, в конечном счете, используемый в BindingSource.NewRow Datatable (в) метод вызывает повторную оценку выражения LINQ после того, как используется в качестве BindingSource с AsDataView()

При первом запуске кода все нормально. Однако после того, как для первого Control был установлен с использованием AsDataView() на IEnumerable, при следующем вызове метода NewRow() на DataTable он повторно оценивает выражение LINQ.

Option Explicit On 
Option Infer Off 
Option Strict On 

Public Class Form1 
    Dim ds As New DataSet() 

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load 
     ds.Tables.Add("Table1") 

     Dim comparisonInteger As Integer = 12 
     ds.Tables("Table1").Columns.Add("ID", GetType(Integer)) 
     ds.Tables("Table1").Columns.Add("IntegerValue", GetType(Integer)) 
     ds.Tables("Table1").Columns.Add("StringValue", GetType(String)) 

     ds.Tables("Table1").Rows.Add({1, 47, "row 1"}) 
     ds.Tables("Table1").Rows.Add({2, 2, "row 1"}) 
     ds.Tables("Table1").Rows.Add({3, 7, "row 1"}) 
     ds.Tables("Table1").Rows.Add({4, 6, "row 1"}) 

     For i As Integer = 0 To 2 
      Dim iterator As Integer = i 
      Dim tb As New TextBox() 

      CreateNewRowIfMissing(comparisonInteger+i) 

      Dim dataValue As EnumerableRowCollection(Of DataRow) = ds.Tables("Table1").AsEnumerable() _ 
                     .Where(Function(v) DirectCast(v("IntegerValue"), Integer) = comparisonInteger + iterator) 

      Dim bs As New BindingSource(dataValue.AsDataView(), Nothing) 
      Dim b As New Binding("Text", bs, "IntegerValue") 
      b.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged 
      tb.DataBindings.Add(b) 

      tb.Location = New Point(10, 10 * Me.Controls.Count+1) 

      Me.Controls.Add(tb) 
     Next 
    End Sub 

    Private Sub CreateNewRowIfMissing(comparisonInteger As Integer) 

     If ds.Tables("Table1").AsEnumerable() _ 
       .Where(Function(v) DirectCast(v("IntegerValue"), Integer) = comparisonInteger) _ 
       .Count() = 0 

      ds.Tables("Table1").Rows.Add(ds.Tables("Table1").NewRow()) 'Prompts the dataValue IEnumerable to begin evaluating again. 
      ds.Tables("Table1").Rows(ds.Tables("Table1").Rows.Count-1)("ID") = ds.Tables("Table1").Rows.Count 
      ds.Tables("Table1").Rows(ds.Tables("Table1").Rows.Count-1)("IntegerValue") = comparisonInteger 
      ds.Tables("Table1").Rows(ds.Tables("Table1").Rows.Count-1)("StringValue") = "row" & ds.Tables("Table1").Rows.Count.ToString() 
     End If 
    End Sub 
End Class 

Чтобы уточнить, я имел обыкновение использовать стандартный DataView с RowFilter как BindingSource, который работал:

Dim dv As New DataView(ds.Tables("Table1")) 
dv.RowFilter = "IntegerValue=" & (comparisonInteger+iteratorValue) 

Однако я чувствовал, что это могло бы быть лучше, чтобы использовать LINQ вместо.

Итак, что здесь происходит? Почему выражение LINQ основано на переоценке DataView при вызове метода DataTableNewRow()? Есть ли способ предотвратить это?

Я надеялся, что AsDataView() создал бы DataView, который будет вести себя так же, как две вышеуказанные строки.

Как потенциал работы вокруг, я обнаружил, что я могу использовать это как BindingSource «s DataSource:

New DataView(dataValue.AsEnumerable().CopyToDataTable())

Однако, я обеспокоен потенциальной производительности с такой линии в будущее, и что я мог бы также использовать DataView с RowFilter, а не LINQ, если ему нужна вышеуказанная строка.

ответ

1

Это нормальное поведение DataTableExtensions.AsEnumerable() и это documented:

перечислимого объект, возвращаемый методом AsEnumerable является постоянно привязан к DataTable, который произвел его. Несколько вызовов на метод AsEnumerable возвратит несколько независимых независимых запросов объектов , которые все связаны с источником DataTable.

Также нормальное поведение DataView который также documented:

DataView не хранит данные, но вместо этого представляет собой соединенный вид соответствующей DataTable.

При вызове DataTable.AsEnumemarble() метода, то EnumerableRowCollection<DatRow>, который возвращает метод сохраняет ссылку на исходный DataTable, а затем использовать его каждый раз, когда вы звоните EnumerableRowCollection(Of T).AsDataView() метод и возвращаете строку из исходной DataTable, который используется для создания коллекции.

Таким образом, в вашем примере dataValue.AsDataView(), который вы использовали в качестве DataSource из BindingSource всегда возвращает текущие строки из DataTable, когда поднял ListChanged событие.

Но если вы назначаете new DataView(data.AsEnumerable().CopyToDataTable()) к DataSource в BindingSource, так как вы используете другой DataTable по телефону CopyToDataTable, изменяя исходный DataTable не оказывает никакого влияния на источнике данных, который ничего не знает о первоначальном DataTable. На самом деле его достаточно, чтобы использовать ниже заявление, как DataSource в BindingSource:

bs.DataSource = dataValue.CopyToDataTable() 

Пример

Положите DataGridView и 3 кнопки на Form, а затем обработать Click событие кнопки, как показано ниже.

DataTable dt; 
EnumerableRowCollection<DataRow> data; 
BindingSource bs; 
private void button1_Click(object sender, EventArgs e) 
{ 
    dt = new DataTable(); 
    dt.Columns.Add("A", typeof(int)); 
    for (int i = 0; i < 5; i++) 
     dt.Rows.Add(i); 
    data = dt.AsEnumerable().Where(x => x.Field<int>(0) >= 3); 
    bs = new BindingSource(); 
    bs.DataSource = data.AsDataView(); 
    bs.RaiseListChangedEvents = true; 
    this.dataGridView1.DataSource = bs; 
} 
private void button2_Click(object sender, EventArgs e) 
{ 
    dt.Rows.Add(dt.AsEnumerable().Max(x => x.Field<int>(0)) + 1); 
} 
private void button3_Click(object sender, EventArgs e) 
{ 
    var row = data.Where(x => x.Field<int>(0) == 3).First(); 
    row[0] = 333; 
    MessageBox.Show(dt.Rows[3][0].ToString()); 
} 

Тест 1

  1. Нажмите на Button1, то DataGridView покажет, 3, 4 в колонке A.
  2. Нажмите на Button2, новая строка с 5, так как значение столбца A будет добавлено и показано сразу же в сетке. Также он показывает MessageBox, который показывает 3, как количество товаров в data.
  3. Нажмите на Button3, 3 будет изменен на 333 и сразу же будет отображаться в виде сетки. Также он показывает 333 как значение ячейки в таблице данных.

Результат → путем включения ListChanged и с помощью AsDataView вы сразу увидите изменения в сетке.

Тест 2

  1. Выключите повышение ListChanged с помощью bs.RaiseListChangedEvents = false;
  2. Нажмите на Button1 будет действовать как тест 1.
  3. Нажмите на Button2 будет больше не показывает новую запись немедленно. Пока он показывает 3 как количество строк в data.
  4. Нажмите, чтобы Button3 больше не будет отображаться измененное значение немедленно. Пока в окне сообщений показано, что данные были изменены в DataTable.

Результат → Если вы отключили событие ListChanged, изменения не будут отображаться в списке немедленно.

Тест 3

  1. Использование bs.DataSource = data.CopyToDataTable();
  2. Set bs.RaiseListChangedEvents = true;
  3. Нажмите на Button1, чтобы заполнить сетку.
  4. Нажмите на Button2, не оказывает никакого влияния на сетку, но показывает 3 как количество строк.
  5. Нажмите на Button3, не оказывает никакого влияния на сетку, но показывает 333 как значение таблицы данных.

Результат → AsDataTable создает новый DataTable, которая не связана с оригинальной DataTable.

+0

Ах, поэтому 'CopyToDataTable', безусловно, сломает мой DataBinding. Вероятно, должен был протестировать это, прежде чем рассматривать его как альтернативу. Во всяком случае, я понимаю, что он ссылается на одну и ту же таблицу (это то, что я хочу) в разделе «IEnumerable».Вы говорите, что когда в «DataTable» добавлена ​​новая строка, это вызывает событие «ListChanged» в «DataView», что приводит к переоценке выражения linq, из которого был создан «DataView»? Насколько я понимаю это правильно? – Interminable

+0

* 1) * Данные, полученные с помощью 'CopyToDataTable', содержат те же значения, но это еще одна ссылка. Это другой 'DataTable'. * 2) Событие 'ListChanged'' DataView' будет поднято при добавлении записи или внесении любых изменений в ее базовую 'DataTable'. * 3) * Событие 'ListChanged'' DataView' приводит к тому, что связанный контроль запрашивает данные, поэтому все данные, включая новые строки, возвращаются в 'DataGridView'. Например, если вы установили 'bs.RaiseListChangedEvents = false', вы не увидите изменений в' DataGridView'. –

+0

Не беспокойтесь о производительности. Отображение новых записей, добавленных в DataTable, не оказывает негативного влияния на производительность. Просто подумайте о своем требовании и о том, что вам нужно. Затем, исходя из вашего требования, выберите решение. Например, если вам нужна привязка данных к исходным записям, вы должны использовать 'AsDataView'. Также, если вы не хотите показывать новые записи (я не знаю, почему), примените фильтр к 'DataView.RowFilter'. –

0

Похоже, что когда обновляется базовый DataTable, выражение LINQ для DataView автоматически переоценивается. Это означает, что LINQ повторно оценивалась, когда новая строка была добавлена ​​к DataTable, а не по вызову NewRow(). Это было не сразу очевидно для меня, как я делал, как действия в одной строке: ds.Tables("Table1").Rows.Add(ds.Tables("Table1").NewRow())

@RezaAghaei отметил, что ListChanged события будет получить рейз, если лежащий в основе DataTable обновляется. Посредством тестирования кажется, что если DataView создается из выражения LINQ, он переоценивает LINQ до, при этом возникает событие ListChanged, если обновляется базовый DataTable.

Как следствие этого, установив BindingSource «s RaiseListChangedEvents свойства не остановит оценку LINQ, а также оценка происходит до ListChanged события поднятых, как представляется, не будет никакого другого события, которое может быть обработано чтобы отменить поведение, поэтому я не думаю, что это невозможно остановить.

Однако, теперь, когда я понимаю, что он делает, имеет смысл переоценить выражение LINQ, когда сама строка добавляется к DataTable, поэтому, вероятно, было бы нецелесообразно остановить ее так или иначе.

Это вызывало некоторую озабоченность для меня, хотя, поскольку новый ряд, который я добавлял, не был полностью настроен, в результате получилось InvalidCastException. Однако в моем примере выше я просто должен был убедиться, что новая строка полностью настроена, прежде чем добавлять ее в DataTable. Таким образом, когда LINQ переоценивается, строка содержит достоверные данные, и выражение выполняется без ошибок.

Так, в приведенном выше примере, метод CreateNewRowIfMissing() просто должен быть отрегулирован на следующее:

Private Sub CreateNewRowIfMissing(comparisonInteger As Integer) 

    If ds.Tables("Table1").AsEnumerable() _ 
      .Where(Function(v) DirectCast(v("IntegerValue"), Integer) = comparisonInteger) _ 
      .Count() = 0 

     Dim newRow As DataRow = ds.Tables("Table1").NewRow() 

     newRow("ID") = ds.Tables("Table1").Rows.Count 
     newRow("IntegerValue") = comparisonInteger 
     newRow("StringValue") = "row" & ds.Tables("Table1").Rows.Count.ToString() 

     ds.Tables("Table1").Rows.Add(newRow) 'Prompts the dataValue IEnumerable to begin evaluating again. 
    End If 
End Sub 

Это гарантирует, что новая строка добавляется только к DataTable после того, как будет готов и содержит достоверные данные , В результате, когда он, наконец, добавлен, выражение LINQ повторно оценивает и не бросает InvalidCastException.

+0

** • ** Неясно, какой тест вы считаете, что событие 'ListChanged' не будет повышаться, если вы добавите запись в' DataTable' → Wrong! он будет поднят. ** • ** Я не уверен, что вы имеете в виду, говоря о переоценке linq, но вам нужно внимательно прочитать первую цитату: * Перечислимый объект, возвращаемый методом AsEnumerable, постоянно связан с DataTable, который его создал . * –

+0

@RezaAghaei При тестировании я ломался в точке, где LINQ была переоценена. Я не ожидал, что событие «ListChanged» будет поднято _after_ LINQ будет переоцениваться. Я соответствующим образом обновил ответ. – Interminable

+0

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