2014-01-24 6 views
0

Я использую ASPX.NET MVC4. Я хочу привести некоторые данные из базы данных через модель, которую я хочу отображать как точки на диаграмме в представлении. На графике я хочу отображать максимум 70 данных (дата, значение), но не более.Оптимизируйте очень медленный следующий код в C#

Моя модель StudentGrades это состоит в следующем

StudentID, ModuleID, TestDate, TestGrade. 

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

Вы знаете, как я могу действительно оптимизировать свой код (путем изменения запроса, структур данных, циклов или чего-либо еще)?

Мой код показан ниже.

var query = (from b in db.StudentGrades 
         where b.StudentID.Equals(InputStudentID) 
         orderby b.Date 
         select b); 

      var dates = query.Select(x => EntityFunctions.DiffDays(query.Min(y => y.Date), x.Date)); 
      var grades = query.Select(x => x.grades); 
      var datestable = dates.ToArray(); 
      var gradetable = grades.ToArray(); 
      List<int?> dateslist = new List<int?>(); 
      List<double> gradelist = new List<double>(); 
      double result = dates.Count() * 1.0/70.0; 
      int pointStep = (int)Math.Ceiling(result); 
      for (int i = 0; i < dates.Count(); i = i + pointStep) 
      { 
       dateslist.Add(datestable.ElementAt(i)); 
       gradelist.Add(gradetable.ElementAt(i)); 
      } 
      ViewBag.dates = dateslist; 
      ViewBag.grades = gradelist; 

Большое спасибо

EDIT

я забыл упомянуть. Я не просто хочу 70 очков. Тогда я мог бы просто взять (70). Я на самом деле хочу, чтобы эти 70 баллов были приняты единообразно из моих данных, основанных на их датах. Так что если, например, у меня есть следующие записи на моей базе данных для конкретного студента, а вместо 70 пунктов я хотел 3:

ModuleID, TestDate, TestGrade 
23, 1 January 2014, 5 
34, 2 January 2014, 54 
45, 3 January 2014, 35 
56, 4 January 2014, 55 
67, 5 January 2014, 35 
78, 6 January 2014, 56 
89, 7 January 2014, 53 
90, 8 January 2014, 55 
94, 9 January 2014, 57 

Я бы выбрал рекорд по 1 января 2014, 4 января 2014 и 7 января 2014 или что-то очень похожее. Я имею в виду, что записи, которые я хочу, должны иметь равное или относительно равное расстояние (в датах) между ними.

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

Edit 2

Кроме того, вы можете придумать действительно умный способ для этого, чтобы быть сделано в запросе (без добавления идентификатора, как меняется моя модель)? Если нет, все в порядке, я тоже не могу.

Большого спасибо

+0

Профилируйте его с помощью инструментов VS или, как минимум, с помощью «Секундомера». В общем, поскольку ваш конечный результат представляет собой небольшой набор точек данных, предназначенных для диаграммы, я бы сделал как можно больше работы в базе данных и рассмотрел агрегированные/ограниченные значения в вашей модели. –

+0

@TimMedora благодарит! Да, фактически передайте как можно больше работы в базе данных, это то, что я хочу. Но проблема в том, как? –

+0

Вы можете использовать трассировку профайлера SQL для просмотра запросов (query), которые в настоящее время отправляются в базу данных по вашему коду. Он покажет вам фактические операторы SQL, а также время выполнения. Это может послужить отправной точкой для вашей собственной хранимой процедуры. Хранимая процедура должна выбирать/агрегировать нужные данные. После того, как вы довольны результатами, вызовите хранимую процедуру с помощью ADO.Net и верните «DataReader» или «DataTable». –

ответ

1

Сначала сделайте один запрос, чтобы получить минимальную дату и сохранить его в minDate.

Тогда вы можете сделать:

var query = from b in db.StudentGrades 
      where b.StudentID == InputStudentID 
      orderby b.Date 
      select x => new { Day = EntityFunctions.DiffDays(minDate, x.Date), 
           Grade = x.grades 
          } 

Тогда получите записи, где Day кратно pointStep:

var results = query.Where(x => x.Day % pointStep == 0).ToList(); 
var dateslist = results.Select(x => x.Day).ToList(); 
var gradelist = results.Select(x => x.Grade).ToList(); 

Теперь вы будете получать только необходимые данные из базы данных, и ничего более , в одном запросе (ну, два, честно говоря). Построение финальных списков происходит в мгновение ока.

+0

Спасибо большое! Я попробую это через несколько часов. Тем не менее, это кажется действительно многообещающим, и, таким образом, вы получили мое преимущество. :) –

1

Термин query.Min(y => y.Date) выполняются для каждой строки. Почему бы вам не выбрать минимальную дату?

var minDate = query.Min(y => y.Date); 
var dates = query.Select(x => EntityFunctions.DiffDays(minDate, x.Date)); 

Кроме того, запрос выполняется для каждого из следующих линий

var datestable = dates.ToArray(); 
var gradetable = grades.ToArray(); 

Таким образом, ваш запрос выполняется (N + 3) раз, где N равно числу строк. Это может произойти на сервере, но все еще довольно медленное решение.

Решение проблемы. Поместите query в массив в самом начале, чтобы избежать нескольких выполнений запроса.

var query = (from b in db.StudentGrades 
        where b.StudentID.Equals(InputStudentID) 
        orderby b.Date 
        select b).ToArray(); // <---- ToArray() does the trick. 

Но все же вы можете немного почистить, сохранив минимальную дату один раз. Это также делает код более читаемым.Попробуйте следующее:

var query = (from b in db.StudentGrades 
        where b.StudentID.Equals(InputStudentID) 
        orderby b.Date 
        select b).ToArray(); 
var minDate = query.Min(y => y.Date); 
var dates = query.Select(x => EntityFunctions.DiffDays(minDate, x.Date)); 
// Nothing changed after this point 
var grades = query.Select(x => x.grades); 
... 

Улучшает ли это качество?

+0

Хммм ... большое спасибо. Я попробую их через несколько часов, потому что сейчас у меня есть курсы. Они кажутся очень полезными. Вы также придумали что-нибудь более быстрое в выборе 70 баллов? Я имею в виду, что это нужно передать по запросу или что-то подобное, что будет быстрее ... –

0

ORMs, такие как Entity Framework, отличные, но вам нужно очень заплатить , чтобы ваше внимание было на то, что вы делаете, или вы в конечном итоге забиваете свою базу данных. Следующее общее правило заключается в том, что запрос выполняется только после вычисления выражения. Вещи, которые вызовут выражение для оценки, - это такие вещи, как перечисление, кастинг в список, массив и т. Д., Запрашивающий счет и т. Д. В принципе, все, что потребует реальных данных, приведет к оценке.

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

Таким образом, основываясь на том, что вы можете в значительной степени мгновенно улучшить время загрузки, вызывая оценку на вашей первой линии:

var query = (from b in db.StudentGrades 
        where b.StudentID.Equals(InputStudentID) 
        orderby b.Date 
        select b).ToList(); 

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

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

var query = (from b in db.StudentGrades 
        where b.StudentID.Equals(InputStudentID) 
        orderby b.Date 
        select b).ToList(); 
ViewBag.dates = query.Select(x => EntityFunctions.DiffDays(query.Min(y => y.Date), x.Date)).Take(70); 
ViewBag.grades = query.Select(x => x.grades).Take(70); 

Вы имели тонну кода там просто, чтобы просто ограничить результат до 70 точек данных; для этого вы можете просто использовать Take и называть это днем. Но на самом деле есть одна окончательная оптимизация, которую вы можете добавить, и на самом деле это может быть билет, поскольку, как я уже говорил, у вас не было действительно сумасшедшего кода для налогообложения базы данных. Поэтому медленность очень вероятна из-за огромного количества строк в этой таблице и того факта, что вы изначально втягиваете все. Итак, переместите вызов ViewBag.dates и ViewBag.grades линий и положите его на query, Перед ToList:

var query = (from b in db.StudentGrades 
        where b.StudentID.Equals(InputStudentID) 
        orderby b.Date 
        select b).Take(70).ToList(); 
ViewBag.dates = query.Select(x => EntityFunctions.DiffDays(query.Min(y => y.Date), x.Date)); 
ViewBag.grades = query.Select(x => x.grades); 

там, теперь ваш запрос будет тянуть только первые 70 строк и будет выполняться немедленно. Эти два выбора, которые вы сделаете, будут обрабатывать данные в памяти, а не выдавать дополнительные запросы.

+0

Спасибо большое Крису. Я забыл упомянуть, что я не просто хочу 70 записей, но 70 записей, которые имеют одинаковое расстояние в днях между двумя последовательными точками. Я объяснил, что лучше на мой ответ –

Смежные вопросы