2009-03-20 3 views
9

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

foreach (DataRow row in data.Rows) 
{ 
    SlowLoadingData slow_stuff = slow_query_results[(int)row["id"]]; 

    row.BeginEdit(); 
    row[column_one] = slow_stuff.One; 
    row[column_two] = slow_stuff.Two; 
    row[column_three] = slow_stuff.Three; 
    row.EndEdit(); 
} 

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

После некоторого исследования я нашел способ сделать это быстро. Во-первых, привяжите DataGridView к BindingSource, который привязан к DataTable, а не непосредственно к DataTable. Затем выполните следующие действия, когда вы внести изменения в DataTable:

binding_source.SuspendBinding(); 
binding_source.RaiseListChangedEvents = false; 
// foreach (DataRow in Data.Rows) ... code above 
binding_source.RaiseListChangedEvents = true; 
binding_source.ResumeBinding(); 
grid.Refresh(); 

Существует проблема, хотя, и это Doozy: код выше предотвращает DataGridView от обнаружения новых строк добавлены в DataTable. Любые новые строки, добавленные в таблицу, не отображаются в сетке. Сетка также может генерировать исключения, если вы используете клавиши со стрелками для перемещения текущего выбора ячейки с нижнего конца сетки, поскольку у базового источника данных больше строк, но сетка не создала строки сетки для их отображения.

Таким образом, возможны два решения, которые я могу видеть:

  1. Есть ли лучший способ для подавления обновления привязки во время внесения изменений в базовой DataTable?

  2. Есть ли простой способ сообщить DataGridView изящно обновить коллекцию строк сетки в соответствии с количеством базовых строк DataTable? (Примечание: Я пытался дозвониться BindingSource.ResetBindings, но это, кажется, вызывает больше исключений, если у вас есть удалены строк из DataTable)

+0

Я не знаю много об использовании источника привязки, но делает ли возобновление привязки перед установкой raiselistchangedevents в true, чтобы какая-либо разница? – eglasius

+0

Я пробовал все возможные порядки вызова методов. Все, что требуется для сетки, чтобы прикрутить, это то, что RaiseListChangedEvents является ложным при добавлении новой строки. –

+0

Вызов функции ResetBindings для принудительного обновления пользовательского интерфейса. – CodingBarfield

ответ

4

Рассматривали ли вы отсоединение DataGrid или BindingSource при заполнении таблицу и повторное подключение после этого? Это может показаться немного уродливым, но это должно быть намного быстрее.

+0

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

+0

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

6

Вы можете попробовать использовать Merge method в DataTable. Я попытаюсь создать простое демонстрационное приложение и разместить его здесь, но идея проста. Когда вы хотите обновить Grid, запросите результаты в новый DataTable, а затем слейте старую таблицу с новой таблицей. Пока обе таблицы имеют первичные ключи (вы можете создать им им память, если они не возвращаются из БД), тогда он должен отслеживать изменения и обновлять DataGridView без проблем. Это также имеет то преимущество, что вы не теряете место пользователя на сетке.

ОК, вот образец. Я создаю форму с двумя кнопками и одним dataGridView. На кнопке 1 щелкните, я заполняю основную таблицу некоторыми данными и привязываю к ней сетку. Затем, при втором щелчке, я создаю другую таблицу с той же схемой. Добавьте к нему данные (некоторые из них имеют один и тот же первичный ключ, а некоторые - новые). Затем они объединяют их обратно в исходную таблицу. Он обновляет сетку, как ожидалось.

public partial class Form1 : Form 
    { 
     private DataTable mainTable; 
     public Form1() 
     { 
      InitializeComponent(); 
      this.mainTable = this.CreateTestTable(); 
     } 

     private void button1_Click(object sender, EventArgs e) 
     { 
      for (int i = 1; i <= 10; i++) 
      { 
       this.mainTable.Rows.Add(String.Format("Person{0}", i), i * i); 
      } 

      this.dataGridView1.DataSource = this.mainTable; 
     } 

     private void button2_Click(object sender, EventArgs e) 
     { 
      DataTable newTable = this.CreateTestTable(); 
      for (int i = 1; i <= 15; i++) 
      { 
       newTable.Rows.Add(String.Format("Person{0}", i), i + i); 
      } 
      this.mainTable.Merge(newTable); 
     } 

     private DataTable CreateTestTable() 
     { 
      var result = new DataTable(); 
      result.Columns.Add("Name"); 
      result.Columns.Add("Age", typeof(int)); 
      result.PrimaryKey = new DataColumn[] { result.Columns["Name"] }; 

      return result; 

     } 
    } 
+0

Хорошее предложение. –

+0

Это кажется эффективным, и оно решает дилемму новой строки, но я вижу два недостатка: сетка, кажется, сбрасывает свой свиток на * выбранную строку * (не то же самое, что и на «место пользователя»), и сливается правильно требует, чтобы вы создали копию всей целевой таблицы (может быть большой). –

+0

1) сброс пользовательского интерфейса точно будет очень сложным - и его трудно определить. 2) Отсутствует «копия всей целевой таблицы». И что в наши дни велико? –

3

Если вы используете BindingSource для связывания сложных данных, важно понимать, что SuspendBinding и ResumeBinding только приостановить и возобновить привязки для текущего элемента. Это позволяет отключить привязку к текущему элементу и изменить кучу его свойств без каких-либо индивидуальных изменений свойства, которое выталкивается в связанный элемент управления. (Это не объясняется в документации для BindingSource, где было бы полезно, о нет: это в документации для CurrencyManager.)

Любые изменения, внесенные в другие элементы в списке, т.е. все, кроме текущий элемент, поднимите событие ListChanged. Если вы отключите эти события, BindingSource перестает сообщать связанному элементу об изменениях в списке, пока вы не включите их снова. Это результат, который вы видели: вы добавляете все свои строки в базовый DataTable, но так как вы отключили ListChanged событий, BindingSource не сообщает об этом DataGridView, и поэтому DataGridView остается пустым.

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

Какие исключения вы получаете после того, как позвоните по телефону ResetBindings? Потому что он отлично подходит для меня, добавляю ли, редактирую, удаляю или удаляю строки из базового DataTable.

+0

Тестирование с помощью кода моего коллеги, мы наблюдали много странности после вызова ResetBindings, включая строки фантомной сетки и исключения IndexOutOfRange, выделенные сеткой. Будем экспериментировать больше сегодня, чтобы узнать, происходит ли что-то еще. –

2

У меня возникла аналогичная проблема. Вот решение, которое еще проще (если оно менее изящно).

Я обнаружил, что это:

dataGridView.DataSource = null; 
dataTable.BeginLoadData(); 
foreach(DataRow row in dataTable.Rows){ 
    //modify row 
} 
dataTable.EndLoadData(); 
dataGridView.DataSource = dataTable; 

... это способ быстрее, чем это:

dataTable.BeginLoadData(); 
foreach(DataRow row in dataTable.Rows){ 
    //modify row 
} 
dataTable.EndLoadData(); 

Cheers - DC

0

Я обнаружил, что использование ResetBindings кажется, чтобы переместить полосу прокрутки и пользователь остается думать, что я сделал? Я обнаружил, что использование bindingList в качестве источника данных с объектом, использующим INotifyPropertyChanged, а затем при редактировании строки (привязанной к объекту). строка не обновлялась до изменения щелчка или выбора в форме.

но вызов dgv.Refresh(), казалось, решил проблему без изменения прокрутки.

0

Я нашел решение, Рави LVS на CodeProject хорошо работает:

BindingSource bs = new BindingSource(); 
DataTable dt = new DataTable(); 

bs.DataSource = dt; 
bs.SuspendBinding(); 
bs.RaiseListChangedEvents = false; 
bs.Filter = "1=0"; 
dt.BeginLoadData(); 

//== some modification on data table 

dt.EndLoadData(); 
bs.RaiseListChangedEvents = true; 
bs.Filter = ""; 

Ссылка на исходную страницу: http://www.codeproject.com/Tips/55730/Achieve-performance-while-updating-a-datatable-bou

1

Просто отправляю это как решение: сделать некоторые нотации к комментариям и постам уже. Метод Merge Table, упомянутый BFree, является очень хорошим методом для использования, и я думаю, что правильный подход не слишком упомянул очень простой и элегантный. Вот мои заметки, почему и большой, я не уверен, что кто-то поймал хиты на сервере для запроса. Операционный заявил в своем комментарии к BFree, что ему нужно будет скопировать таблицу, чтобы сделать то, что ему нужно, конечно же, какая таблица, которая была я не уверен, потому что его код:

foreach (DataRow row in data.Rows) 

Эти строки приходят из его таблица называлась данными, где требуется копия - у него уже есть.

Тогда вот что-то, что просто смахивает прямо на каждой итерации этого цикла:

SlowLoadingData slow_stuff = slow_query_results[(int)row["id"]]; 

Is ОП действительно запросов к базе данных каждой итерации этих строк (что, если это большой стол мы говорим 100 000 строк +). Подумайте о загрузке на Сервер (его приложение тоже должно сгенерировать этот запрос!), А также объем трафика, который он размещает в сети, чтобы сделать это! ЕСЛИ это единственное приложение, возможно, это нормально, но даже в этом я не хотел бы этого делать, если бы хотел быть эффективным.

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

SlowLoadingData Page1_SlowLoadingData = slow_query_results[Page1] as DataTable; 
data.Merge(Page1_SlowLoadingData); 

SlowLoadingData Page2_SlowLoadingData = slow_query_results[Page2] as DataTable; 
data.Merge(Page2_SlowLoadingData); 
Смежные вопросы