оптимизация обновлений DataTable, привязанных к DataGridView

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

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!)

7 ответов


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


вы можете попробовать использовать merge метод на DataTable. Я попытаюсь создать простое демо-приложение и опубликовать его здесь, но идея проста. Если вы хотите обновить сетку, запросите результаты в новую таблицу DataTable, а затем объедините старую таблицу с новой таблицей. Пока обе таблицы имеют первичные ключи (вы можете создать их im-память, если они не возвращаются из БД), он должен отслеживать изменения и легко обновлять DataGridView. Он также имеет преимущество не потеря места пользователей в сетке.

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

    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;

        }
    }

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

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

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

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


я столкнулся с подобной проблемой. Вот решение, которое еще проще (хотя и менее элегантно).

Я нашел, что это:

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();

Ура -- DC


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

foreach (DataRow row in data.Rows)

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

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

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

действительно ли OP запрашивает базу данных каждую итерацию этих строк (что, если это большая таблица, мы говорим о 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);

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

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


Я нахожу решение Ravi 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