DataTable.Select и проблема производительности в C # - PullRequest
4 голосов
/ 21 июля 2010

Я импортирую данные из трех файлов с разделителями табуляции в DataTables, и после этого мне нужно пройти через каждую строку главной таблицы и найти все строки в двух дочерних таблицах.Для каждого массива DataRow [], который я нашел в дочерних таблицах, я должен снова пройти по каждой строке отдельно и проверить значения, основанные на разных параметрах, и в конце мне нужно создать итоговую запись, которая будет объединением главной и двух дочернихстолбцы таблицы.Теперь я сделал это, и он работает, но проблема в его производительности.Я использую DataTable.Select, чтобы найти все дочерние строки из дочерней таблицы, что, я считаю, делает его очень медленным.Пожалуйста, помните, что в таблице нет первичного ключа, поскольку допустимы повторяющиеся строки.На данный момент у меня есть 1200 строк в основной таблице и около 8000 строк в дочерней таблице, и общее время, необходимое для этого, составляет 8 минут.

Любая идея, как я могу повысить производительность.Заранее спасибо

Код ниже ***************

 DataTable rawMasterdt = importMasterFile();
 DataTable rawDespdt = importDescriptionFile();

        dsHelper = new DataSetHelper();
        DataTable distinctdt = new DataTable();
        distinctdt = dsHelper.SelectDistinct("DistinctOffers", rawMasterdt, "C1");

        if (distinctdt.Rows.Count > 0)
        {
            int count = 0;
                foreach (DataRow offer in distinctdt.Rows)
                {
                    string exp = "C1 = " + "'" + offer[0].ToString() + "'" + "";
                    DataRow masterRow = rawMasterdt.Select(exp)[0];

                    count++;
                    txtBlock1.Text = "Importing Offer " + count.ToString() + " of " + distinctdt.Rows.Count.ToString(); 
                    if (masterRow != null )
                        {
                            Product newProduct = new Product();

                            newProduct.Code = masterRow["C4"].ToString();
                            newProduct.Name = masterRow["C5"].ToString();
                          //  -----
                            newProduct.Description = getProductDescription(offer[0].ToString(), rawDespdt);
                            newProduct.Weight = getProductWeight(offer[0].ToString(), rawDespdt);
                            newProduct.Price = getProductRetailPrice(offer[0].ToString(), rawDespdt);
                            newProduct.UnitPrice = getProductUnitPrice(offer[0].ToString(), rawDespdt);
                          //  ------- more functions similar to above here

                            productList.Add(newProduct);
                        }
                }
                txtBlock1.Text = "Import Completed";
 public string getProductDescription(string offercode, DataTable dsp)
    {
        string exp = "((C1 = " + "'" + offercode + "')" + " AND ( C6 = 'c' ))";
        DataRow[] dRows = dsp.Select( exp);
        string descrip = "";
        if (dRows.Length > 0)
        { 
            for (int i = 0; i < dRows.Length - 1; i++)
            {
              descrip = descrip + " " + dRows[i]["C12"];
            }
        }
        return descrip;

    }

Ответы [ 6 ]

6 голосов
/ 28 октября 2015

.Net 4.5 и проблема все еще там.

Вот результаты простого теста, в котором DataTable.Select и различные реализации словаря сравниваются по времени ЦП (результаты в миллисекундах).

    #Rows Table.Select  Hashtable[] SortedList[] Dictionary[]
     1000        43,31         0,01         0,06         0,00
     6000       291,73         0,07         0,13         0,01
    11000       604,79         0,04         0,16         0,02
    16000       914,04         0,05         0,19         0,02
    21000      1279,67         0,05         0,19         0,02
    26000      1501,90         0,05         0,17         0,02
    31000      1738,31         0,07         0,20         0,03

Проблема:

Метод DataTable.Select внутренне создает экземпляр класса System.Data.Select, а этот класс «Выбор» создает индексы на основе полей (столбцов), указанных в запросе. Класс Select повторно использует созданные им индексы, но реализация DataTable не использует экземпляр класса Select повторно, поэтому индексы создаются заново каждый раз, когда вызывается DataTable.Select. (Такое поведение можно наблюдать декомпиляцией System.Data)

Решение:

Предположим, следующий запрос

DataRow[] rows = data.Select("COL1 = 'VAL1' AND (COL2 = 'VAL2' OR COL2 IS NULL)");

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

Dictionary<string, List<DataRow>> di = new Dictionary<string, List<DataRow>>();

foreach (DataRow dr in data.Rows)
{
    string key = (dr["COL1"] == DBNull.Value ? "<NULL>" : dr["COL1"]) + "//" + (dr["COL2"] == DBNull.Value ? "<NULL>" : dr["COL2"]);
    if (di.ContainsKey(key))
    {
        di[key].Add(dr);
    }
    else
    {
        di.Add(key, new List<DataRow>());
        di[key].Add(dr);
    }
}

Запрос словаря (может потребоваться несколько запросов) для фильтрации строк и объединения результатов в список

string key1 = "VAL1//VAL2";
string key2 = "VAL1//<NULL>";
List<DataRow>() results = new List<DataRow>();
if (di.ContainsKey(key1))
{
    results.AddRange(di[key1]);
}
if (di.ContainsKey(key2))
{
    results.AddRange(di[key2]);
}
3 голосов
/ 03 июня 2011

Вы можете значительно ускорить его, используя словарь. Например:

if (distinctdt.Rows.Count > 0)
{
    // build index of C1 values to speed inner loop
    Dictionary<string, DataRow> masterIndex = new Dictionary<string, DataRow>();
    foreach (DataRow row in rawMasterdt.Rows)
        masterIndex[row["C1"].ToString()] = row;

    int count = 0;
    foreach (DataRow offer in distinctdt.Rows)
    {

Тогда вместо

    string exp = "C1 = " + "'" + offer[0].ToString() + "'" + "";
    DataRow masterRow = rawMasterdt.Select(exp)[0];

Вы бы сделали это

DataRow masterRow;
if (masterIndex.ContainsKey(offer[0].ToString())
    masterRow = masterIndex[offer[0].ToString()];
else
    masterRow = null;
2 голосов
/ 27 июля 2012

Если вы создаете DataRelation между родительским и дочерним DataTables, вы можете искать дочерние строки, вызывая DataRow.GetChildRows (DataRelation) в родительской строке (соответственно DataRow.GetChildRelName в случае типизированных DataSets). При поиске будет применен поиск TreeMap, и производительность должна быть хорошей даже при большом количестве дочерних строк.

В случае, если вам нужно искать строки на основе других критериев, отличных от внешних ключей DataRelation, я рекомендую использовать DataView.Sort / DataView.FindRows () вместо DataTable.Select (), как только вам потребуется выполнить запрос данные более одного раза. DataView.FindRows () будет основан на поиске TreeMap (O (log (N)), где DataTable.Select () должен сканировать все строки (O (N)). Эта статья содержит более подробную информацию: http://arnosoftwaredev.blogspot.com/2011/02/when-datatableselect-is-slow-use.html

0 голосов
/ 31 июля 2018

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

Для тех, кто придет позжедата ... вот что я нашел.

Производительность DataTable.Select(condition) очень чувствительна к природе и структуре «состояния», которое вы предоставляете.Для меня это выглядит как ошибка (где бы я мог сообщить об этом в Microsoft?), Но может быть просто причудой.

Я написал ряд тестов, чтобы продемонстрировать проблему, котораяимеют следующую структуру:

  1. Определение данных с помощью нескольких простых столбцов, например:

    var dataTable = new DataTable ();
    varidCol = dataTable.Columns.Add ("Id", typeof (Int32));
    dataTable.Columns.Add ("Code", typeof (string));
    dataTable.Columns.Add ("Имя",typeof (string));
    dataTable.Columns.Add ("FormationDate", typeof (DateTime));
    dataTable.Columns.Add ("Income", typeof (Decimal));
    dataTable.Columns.Add ("ChildCount", typeof (Int32));
    dataTable.Columns.Add ("Foreign", typeof (Boolean));
    dataTable.PrimaryKey = new DataColumn [1] {idCol};

  2. Заполните таблицу 40000 записями, каждая с уникальным полем «Код».

  3. Выполните пакет «выбор» (каждый с различными параметрами)) против данныхВ таблице используются два одинаковых, но по-разному отформатированных запроса и записи, и сравнивается общее время, затрачиваемое каждым из двух форматов.

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

Q1: [Код] = 'XX'

Q2: ([Код] = 'XX')

[Я делаю несколько вызовов Select, используя два вышеупомянутых запроса, на каждой итерации я заменяю XX действительным кодом, который существует в таблице данных]. Результат?

Сравнение времени для 320 поисков против 40000записи: общее время поиска 180 мсек без скобок, общее время поиска 6871 мсек с поиском С скобками

Да - в 38 раз медленнее, если у вас есть дополнительные скобки, окружающие условие.Есть и другие сценарии, которые реагируют по-разному.

Например, [Code] = '{searchCode}' OR 1=0 против ([Code] = '{searchCode}' OR 1=0) выполняется аналогичное (медленное) время выполнения, но:

[Code] = '{searchCode}' AND 1=1 против ([Code] = '{searchCode}' AND 1=1) снова показывает, что версия без скобок закрываетсяв 40 раз быстрее.

Я не исследовал все сценарии, но кажется, что введение скобок - либо избыточно вокруг простой проверки сравнения, либо, как требуется для указания приоритета подвыражения - или наличия'ИЛИ' значительно замедляет запрос.

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

0 голосов
/ 03 июня 2011

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

  • Строковое чтение файла основного текста в память.Поместите основную запись в словарь в качестве ключа.Добавьте его в набор данных (1 проход через мастер).

  • Строковое чтение дочернего текстового файла, добавьте его в качестве значения для соответствующей основной записи в словаре, созданном выше

  • Теперь у вас есть все в словаре в памяти, только делает 1 проход через каждый файл.Сделайте последний проход по словарю / потомкам, обработайте каждый столбец и выполните окончательные вычисления.

0 голосов
/ 21 июля 2010

DataTables могут иметь отношения с другими DataTables в DataSet.См. http://msdn.microsoft.com/en-us/library/ay82azad%28VS.71%29.aspx для небольшого обсуждения и в качестве отправной точки для просмотра.У меня не так много опыта их использования, но, насколько я понимаю, они будут делать то, что вы хотите (при условии, что ваши таблицы в подходящем формате).Я бы предположил, что они имеют большую эффективность, чем ручной процесс, но я могу ошибаться.Возможно, стоит посмотреть, работают ли они на вас, и провести сравнительный анализ, чтобы увидеть, являются ли они улучшением или нет ...

...