2015-11-01 2 views
1

Мне очень нужна помощь с этим и не удалось найти соответствующие ответы после нескольких часов поиска.Entity Framework 6 - Группировка по заказу First() занимает слишком много времени

MySQL, Entity Framework 6, база данных с несколькими миллионами записей, запись выглядит следующим образом:

Indexint (11) NOT NULL
TaskIDint (11) NOT NULL
DeviceIDbigint (20) NOT NULL
Commentslongtext NULL
ExtendedResultslongtext NULL
RunResultint (11) NOT NULL
JobResultint (11) NOT NULL
JobResultValuedouble NOT NULL
ReporterIDbigint (20) NOT NULL
FieldIDbigint (20) NOT NULL
TimeOfRundatetime NOT NULL

Что мне нужно, чтобы получить все записи для конкретного TaskId, затем группу по DeviceID и сортировать по TimeOfRun для того, чтобы получить последние данные для каждого идентификатора устройства в конкретном идентификаторе задачи.

Это мой код:

List<JobsRecordHistory> newH = db.JobsRecordHistories.AsNoTracking().Where(x => x.TaskID == taskID).GroupBy(x => x.DeviceID). 
       Select(x => x.OrderByDescending(y => y.TimeOfRun).FirstOrDefault()).ToList(); 

Но это генерируется запрос:

{SELECT 

Apply1. Index, Apply1. TaskID, Apply1. DEVICEID1 AS DeviceID, Apply1. RunResult, Apply1. JobResult, Apply1. JobResultValue, Apply1. ExtendedResults, Apply1. Comments, Apply1. ReporterID, Apply1. FieldID, Apply1. TimeOfRun ОТ (SELECT Project2. p__linq__0, Project2. DeviceID, (SELECT Project3. Index ОТ JobsRecordHistories КАК Project3 ГДЕ (Project3. TaskID = @ p__linq__0) И (Project2. DeviceID = Project3. DeviceID) ORDER BY Project3. TimeOfRun DESC LIMIT 1) AS Index, (SELECT Project3. TaskID ОТ JobsRecordHistories КАК Project3 ГДЕ (Project3. TaskID = @ p__linq__0) И (Project2. DeviceID = Project3. DeviceID) ORDER BY Project3.TimeOfRun DESC LIMIT 1) TaskID, (SELECT Project3. DeviceID ОТ JobsRecordHistories КАК Project3 ГДЕ (Project3. TaskID = @ p__linq__0) И (Project2. DeviceID = Project3. DeviceID) ORDER BY Project3. TimeOfRun по убыванию ПРЕДЕЛ 1) КАК DEVICEID1, (SELECT Project3. RunResult ОТ JobsRecordHistories КАК ГДЕ (Project3. TaskID = @ p__linq__0) И (Project2. DeviceID = Project3. DeviceID) ORDER BY Project3. TimeOfRun DESC LIMIT 1) А.С. RunResult, (SELECT Project3. JobResult ОТ JobsRecordHistories КАК Project3 ГДЕ (Project3. TaskID = @ p__linq__0) И (Project2. DeviceID = Project3. DeviceID) ORDER BY Project3. TimeOfRun по убыванию ПРЕДЕЛ 1) КАК JobResult, (SELECT Project3. JobResultValue ОТ JobsRecordHistories КАК Project3 WHERE (Project3. TaskID = @ p__linq__0) И (Project2. DeviceID = Project3. DeviceID) ORDER BY Project3. TimeOfRun DESC LIMIT 1) А.С. JobResultValue, (SELECT Project3. ExtendedResults ОТ JobsRecordHistories КАК Project3 ГДЕ (Project3. TaskID = @ p__linq__0) И (Project2. DeviceID = Project3. DeviceID) ORDER BY Project3. TimeOfRun по убыванию ПРЕДЕЛ 1) КАК ExtendedResults, (SELECT Project3. Comments ОТ JobsRecordHistories КАК Project3 ГДЕ (Project3. TaskID = @ p__linq__0) И (Project2. DeviceID = Project3. DeviceID) ORDER BY Project3. TimeOfRun DESC LIMIT 1) А.С. Comments, (SELECT Project3. ReporterID ОТ JobsRecordHistories КАК Project3 ГДЕ (Project3. TaskID = @ p__linq__0) И (Project2. DeviceID = Project3. DeviceID) ORDER BY Project3. TimeOfRun по убыванию ПРЕДЕЛ 1) AS ReporterID, (SELECT Project3.FieldID ОТ JobsRecordHistories КАК Project3 ГДЕ (Project3. TaskID = @ p__linq__0) И (Project2. DeviceID = Project3. DeviceID) ORDER BY Project3. TimeOfRun DESC LIMIT 1) А.С. FieldID, (SELECT Project3. TimeOfRun ОТ JobsRecordHistories КАК Project3 ГДЕ (Project3. TaskID = @ p__linq__0) И (Project2. DeviceID = Project3. DeviceID) ORDER BY Project3. TimeOfRun по убыванию ПРЕДЕЛ 1) КАК TimeOfRun ОТ (SELECT @ p__linq__0 А.С. p__linq__0, Distinct1. DeviceID FROM (SELECT DISTINCT Extent1. DeviceID ОТ JobsRecordHistories AS Extent1 ГДЕ Extent1. TaskID = @ p__linq__0) AS Distinct1) AS Project2) AS Apply1}

Который занимает слишком много времени.
Я не знаю SQL достаточно хорошо, признаю, но если вставить инструкцию ToList() после инструкции WHERE, то я получаю результаты гораздо быстрее, хотя это все еще не так, потому что есть много не- необходимые данные, которые база данных переходит к моему приложению в этой ситуации, и она по-прежнему медленная = 30 секунд для 40 тыс. записей.

Я также попытался это:

Dictionary<long, DateTime> DeviceIDAndTime = db.JobsRecordHistories.AsNoTracking().Where(x => x.TaskID == taskID).GroupBy(x => x.DeviceID) 
       .Select(g => new DeviceIDaAndTime { deviceID = g.Key, timeOfRun = g.Max(gi => gi.TimeOfRun) }).ToDictionary(x => x.deviceID, x => x.timeOfRun); 

Для того, чтобы использовать словарь таким образом:

   List<JobsRecordHistory> newH = db.JobsRecordHistories.AsNoTracking().Where(x => DeviceIDAndTime.Keys.Contains(x.DeviceID) && x.TimeOfRun == DeviceIDAndTime[x.DeviceID]).ToList(); 

Но я получаю эту ошибку:

Additional information: LINQ to Entities does not recognize the method 'System.DateTime get_Item(Int64)' method, and this method cannot be translated into a store expression. 

который имеет смысл причину от что я понимаю, при сравнении значения timeOfRun со значением словаря LINQ требуется определенное значение, а не коллекция, когда составление запроса.

Странно, что я не нашел никакого связанного сообщения и что другие люди не столкнулись с этой проблемой. Наверное, я что-то пропустил.

Цените любую помощь, спасибо

+1

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

ответ

1

Наконец понял это и повышение производительности.
Мне нужен был запрос и подзапрос, И мне нужна была функция MAX вместо ORDER, потому что меня не волнует порядок результатов, я забочусь только о самой большой (timeOfRun).
Кроме того, все упростилось, как только я заметил, что более крупный индексный столбец (мой ПК, автоматический прирост) означает более свежие данные, поэтому мне не нужен MAX (timeOfRun), вместо этого я использовал MAX (Index), хотя я вполне конечно, это сработало бы так же.

Это мой LINQ:

var historyQuery = db.JobsRecordHistories.AsNoTracking().Where(y => y.TaskID == taskID && 
            db.JobsRecordHistories.Where(x => x.TaskID == taskID).GroupBy(x => x.DeviceID).Select(g => g.Max(i => i.Index)).Contains<int>(y.Index)); 

И это сгенерированный SQL:

{SELECT 

Extent1. Index, Extent1. TaskID, Extent1. DeviceID, Extent1. RunResult, Extent1. JobResult, Extent1. JobResultValue, Extent1. ExtendedResults, Extent1. Comments, Extent1. ReporterID, Extent1. FieldID, Extent1. TimeOfRun ОТ JobsRecordHistories КАК Extent1 ГДЕ (Extent1. TaskID = @ p__linq__0) И (EXISTS (SELECT 1 AS C1 FROM (SELECT Extent2. DeviceID А.С. K1, MAX (Extent2. Index) КАК A1 ОТ JobsRecordHistories КАК Extent2 ГДЕ Extent2. TaskID = @ p__linq__1 ГРУППА ПО Extent2. DeviceID) AS GroupBy1 WHERE GroupBy1. A1 = Extent1. Index))}

Я надеюсь, что это поможет кому-то, так как это у меня ушло 1,5 дня прибегая к помощи, глядя на запросы SQL, LINQ, отладки и оптимизации

1

Дают синтаксис запроса вместо метода на основе выстрела.
Я не тестировал это локально, но вы можете увидеть улучшенное поколение sql.
Или, по крайней мере, может быть, такой подход может привести вас вниз правильный путь

using System; 
using System.Data.Entity; 
using System.Linq; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 

namespace EF.CodeFirst 
{ 
    [TestClass] 
    public class UnitTest1 
    { 
     [TestMethod] 
     public void TestMethod1() 
     { 
      using (var db = new TestDbContext()) 
      { 
       var taskId = 1; 
       var query = from job in db.JobRecordHistories 
        where job.TaskId == taskId 
        orderby job.TimeOfRun descending 
        group job by job.DeviceId 
        into deviceGroup 
        select deviceGroup; 

       foreach (var deviceGroup in query) 
       { 
        foreach (var jobRecordHistory in deviceGroup) 
        { 
         Console.WriteLine("DeviceId '{0}', TaskId'{1}' Runtime'{2}'", jobRecordHistory.DeviceId, 
          jobRecordHistory.TaskId, jobRecordHistory); 
        } 
       } 
      } 
     } 
    } 

    public class TestDbContext : DbContext 
    { 
     public DbSet<JobRecordHistory> JobRecordHistories { get; set; } 
    } 

    public class JobRecordHistory 
    { 
     public int Id { get; set; } 
     public int TaskId { get; set; } 
     public int DeviceId { get; set; } 
     public DateTime TimeOfRun { get; set; } 
    } 
} 
+0

Спасибо! ваш запрос отличается от моего, он заказывает по дате, а затем группы по устройству, что означает, что он заказывает ВСЕ, а не только внутри группы. в любом случае, я приурочил ваш запрос и тот же запрос, используя lambda (в то же время), и это заняло 10 секунд (результаты 41k) по сравнению с моим обходным запросом с «ToList» после того, где заняло 28 секунд. Так что спасибо! Я буду использовать его, но я думаю, что есть еще лучший способ. – Alonzzo2

+0

Теперь я вижу, что он фактически не заказывает TimeOfRun, поэтому этот запрос на самом деле просто дал мне список заданий, 1 на DeviceID, независимо от времени ... – Alonzzo2