2015-09-10 3 views
7

В настоящее время у меня есть запрос, который не требует много времени и иногда сбой из-за количества данных в базе данных.Ускорение запроса linq entitiy

Может ли кто-нибудь заметить что-нибудь, что я могу сделать, чтобы ускорить его?

public IList<Report> GetReport(CmsEntities context, long manufacturerId, long? regionId, long? vehicleTypeId) 
     { 
      var now = DateTime.Now; 
      var today = new DateTime(now.Year, now.Month, 1); 
      var date1monthago = today.AddMonths(-1); 
      var date2monthago = today.AddMonths(-2); 
      var date3monthago = today.AddMonths(-3); 
      var date4monthago = today.AddMonths(-4); 
      var date5monthago = today.AddMonths(-5); 
      var date6monthago = today.AddMonths(-6); 
      today = TimeManager.EndOfDay(new DateTime(now.AddMonths(-1).Year, today.AddMonths(-1).Month, DateTime.DaysInMonth(now.Year, today.AddMonths(-1).Month)));    
      var query = from item in context.Invoices 
         where item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Select(x => x.ManufacturerId).Contains(manufacturerId) 
         && (item.InvoiceDate >= date6monthago && item.InvoiceDate <= today) 
         && (regionId.HasValue && regionId.Value > 0 ? item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Select(x => x.RegionId).Contains(regionId.Value) : true) 
         && (item.InvType == "I" || item.InvType == null) 
         && (vehicleTypeId.HasValue && vehicleTypeId.Value > 0 ? item.Repair.Job.Vehicle.Model.VehicleTypes.Select(x => x.Id).Contains(vehicleTypeId.Value) : true) 
         select item; 

      var query2 = from item in query 
         group item by new { item.Repair.Job.Bodyshop } into g 
         let manufJobs = query.Where(x => x.Repair.Job.Vehicle.Model.ManufacturerId == manufacturerId && x.Repair.Job.BodyshopId == g.Key.Bodyshop.Id) 
         let allJobs = query.Where(x => x.Repair.Job.BodyshopId == g.Key.Bodyshop.Id) 
         select new tReport 
         {          
    MonthSixManufJobTotal = manufJobs.Where(x => x.InvoiceDate.Month == date6monthago.Month && x.InvoiceDate.Year == date6monthago.Year).GroupBy(x => x.Repair.Job).Count(), 
    MonthSixJobTotal = allJobs.Where(x => x.InvoiceDate.Month == date6monthago.Month && x.InvoiceDate.Year == date6monthago.Year).GroupBy(x => x.Repair.Job).Count(), 

    MonthFiveManufJobTotal = manufJobs.Where(x => x.InvoiceDate.Month == date5monthago.Month && x.InvoiceDate.Year == date5monthago.Year).GroupBy(x => x.Repair.Job).Count(), 
    MonthFiveJobTotal = allJobs.Where(x => x.InvoiceDate.Month == date5monthago.Month && x.InvoiceDate.Year == date5monthago.Year).GroupBy(x => x.Repair.Job).Count(), 

    MonthFourManufJobTotal = manufJobs.Where(x => x.InvoiceDate.Month == date4monthago.Month && x.InvoiceDate.Year == date4monthago.Year).GroupBy(x => x.Repair.Job).Count(), 
    MonthFourJobTotal = allJobs.Where(x => x.InvoiceDate.Month == date4monthago.Month && x.InvoiceDate.Year == date4monthago.Year).GroupBy(x => x.Repair.Job).Count(), 

    MonthThreeManufJobTotal = manufJobs.Where(x => x.InvoiceDate.Month == date3monthago.Month && x.InvoiceDate.Year == date3monthago.Year).GroupBy(x => x.Repair.Job).Count(), 
    MonthThreeJobTotal = allJobs.Where(x => x.InvoiceDate.Month == date3monthago.Month && x.InvoiceDate.Year == date3monthago.Year).GroupBy(x => x.Repair.Job).Count(), 

    MonthTwoManufJobTotal = manufJobs.Where(x => x.InvoiceDate.Month == date2monthago.Month && x.InvoiceDate.Year == date2monthago.Year).GroupBy(x => x.Repair.Job).Count(), 
    MonthTwoJobTotal = allJobs.Where(x => x.InvoiceDate.Month == date2monthago.Month && x.InvoiceDate.Year == date2monthago.Year).GroupBy(x => x.Repair.Job).Count(), 

    MonthOneManufJobTotal = manufJobs.Where(x => x.InvoiceDate.Month == date1monthago.Month && x.InvoiceDate.Year == date1monthago.Year).GroupBy(x => x.Repair.Job).Count(), 
    MonthOneJobTotal = allJobs.Where(x => x.InvoiceDate.Month == date1monthago.Month && x.InvoiceDate.Year == date1monthago.Year).GroupBy(x => x.Repair.Job).Count(), 

    ManufTotal = manufJobs.GroupBy(x => x.Repair.Job).Count(), 
    Total = allJobs.GroupBy(x => x.Repair.Job).Count(), 

    PercentageOf = ((decimal)manufJobs.GroupBy(x => x.Repair.Job).Count()/(decimal)allJobs.GroupBy(x => x.Repair.Job).Count()) * 100 
         }; 

      return query2.OrderBy(x => x).ToList(); 
     } 

EDIT

var query = from item in context.Invoices.AsNoTracking() 
        where item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Any(x => x.ManufacturerId == manufacturerId) 
        && (item.InvoiceDate >= date12monthago && item.InvoiceDate <= today) 
        && (item.InvType == "I" || item.InvType == null) 
        select item; 

     if (regionId.HasValue && regionId.Value > 0) 
     { 
      query = query.Where(item => item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Select(x => x.RegionId).Contains(regionId.Value)); 
     } 

     if (vehicleTypeId.HasValue && vehicleTypeId.Value > 0) 
     { 
      query = query.Where(item => item.Repair.Job.Vehicle.Model.VehicleTypes.Select(x => x.Id).Contains(vehicleTypeId.Value)); 
     } 


       var query2 = from item in hey 
        group item by new { item.Repair.Job.Bodyshop, item.InvoiceDate.Month } into m 
        select new TReport 
        { 
         Bodyshop = m.Key.Bodyshop.Name, 
         Bays = m.Key.Bodyshop.Bays, 
         Region = m.Key.Bodyshop.Manufacturer2Bodyshop.FirstOrDefault(x => x.ManufacturerId == manufacturerId).Region.Name, 
         BodyshopCode = m.Key.Bodyshop.Manufacturer2Bodyshop.FirstOrDefault(x => x.ManufacturerId == manufacturerId).BodyshopCode, 
         Total = m.Count(), 
         ManufTotal = m.Where(x => x.Repair.Job.Vehicle.Model.ManufacturerId == manufacturerId).Count(), 
         Totals = m.GroupBy(j => j.InvoiceDate.Month).Select(j => new TPercentReportInner 
         { 
          Month = j.Key, 
          ManufTotal = j.Where(x => x.Repair.Job.Vehicle.Model.ManufacturerId == manufacturerId).Count(), 
          AllTotal = j.Count() 
         }) 
        }; 

Ive сократить запрос вниз. Но даже это сейчас хуже, чем раньше?

+0

Какие показатели доступны на стол (ы)? – Caramiriel

+0

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

+0

Я думаю, что на данный момент только столбец id на каждой таблице не является столбцом даты, но я постараюсь сделать это также – Beginner

ответ

5

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

var query = from item in context.Invoices.AsNoTracking() 
      where item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Select(x => x.ManufacturerId).Contains(manufacturerId) 
      && (item.InvoiceDate >= date12monthago && item.InvoiceDate <= today) 
      && (item.InvType == "I" || item.InvType == null) 
      select item; 

if (regionId.HasValue && regionId.Value > 0) 
    query=query.Where(item=>item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Select(x => x.RegionId).Contains(regionId.Value)); 

if (vehicleTypeId.HasValue && vehicleTypeId.Value > 0) 
    query=query.Where(item=>item.Repair.Job.Vehicle.Model.VehicleTypes.Select(x => x.Id).Contains(vehicleTypeId.Value)); 

var query2 = from item in query 
      group item by new { item.InvoiceDate.Month, item.Repair.Job.Bodyshop } into g 
      select new TReport 
      { 
       BodyshopId = g.Key.Bodyshop.Id, 
       Month = g.Key.Month, 
       MonthAllJobTotal = g.Count() 
      }; 

return query2.ToList(); 

Вы можете также проверить, чтобы увидеть если преобразование .Select(x=>x.id).Contains(id) или .Any(x=>x.Id==id) выполняется быстрее, хотя я бы подумал, что они будут похожи в плане запроса и скорости выполнения. Это даст вам:

var query = from item in context.Invoices.AsNoTracking() 
      where item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Any(m=>m.ManufacturerId==manufacturerId) 
      && (item.InvoiceDate >= date12monthago && item.InvoiceDate <= today) 
      && (item.InvType == "I" || item.InvType == null) 
      select item; 

if (regionId.HasValue && regionId.Value > 0) 
    query=query.Where(item=>item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Any(m=>m.RegionId==regionId.Value)); 

if (vehicleTypeId.HasValue && vehicleTypeId.Value > 0) 
    query=query.Where(item=>item.Repair.Job.Vehicle.Model.VehicleTypes.Any(v=>v.Id==vehicleTypeId.Value)); 

var query2 = from item in query 
      group item by new { item.InvoiceDate.Month, item.Repair.Job.Bodyshop } into g 
      select new TReport 
      { 
       BodyshopId = g.Key.Bodyshop.Id, 
       Month = g.Key.Month, 
       MonthAllJobTotal = g.Count() 
      }; 

return query2.ToList(); 

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

Я бы тогда очистить и стандартизировать запрос путем удаления жёстко прописанные ManufacturerId, а также, который даст вам:

var query = from item in context.Invoices.AsNoTracking() 
      where (item.InvoiceDate >= date12monthago && item.InvoiceDate <= today) 
      && (item.InvType == "I" || item.InvType == null) 
      select item; 

if (manufacturerId.HasValue && manufacturerId.Value > 0) 
    query=query.Where(item=>item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Any(m=>m.ManufacturerId==manufacturerId)); 

if (regionId.HasValue && regionId.Value > 0) 
    query=query.Where(item=>item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Any(m=>m.RegionId==regionId.Value)); 

if (vehicleTypeId.HasValue && vehicleTypeId.Value > 0) 
    query=query.Where(item=>item.Repair.Job.Vehicle.Model.VehicleTypes.Any(v=>v.Id==vehicleTypeId.Value)); 

var query2 = from item in query 
      group item by new { item.InvoiceDate.Month, item.Repair.Job.Bodyshop } into g 
      select new TReport 
      { 
       BodyshopId = g.Key.Bodyshop.Id, 
       Month = g.Key.Month, 
       MonthAllJobTotal = g.Count() 
      }; 

return query2.ToList(); 

, а затем, наконец, я хотел бы вернуть IQueryable вместо списка, так что если вы не «т необходимости один или несколько столбцов, они могут быть исключены из окончательного запроса, так как:

public IQueryable<Report> GetReport(CmsEntities context, long? manufacturerId, long? regionId, long? vehicleTypeId) 
    { 
{ 
     var now = DateTime.Now; 
     var today = new DateTime(now.Year, now.Month, 1); 
     var date1monthago = today.AddMonths(-1); 
     var date2monthago = today.AddMonths(-2); 
     var date3monthago = today.AddMonths(-3); 
     var date4monthago = today.AddMonths(-4); 
     var date5monthago = today.AddMonths(-5); 
     var date6monthago = today.AddMonths(-6); 
     today = TimeManager.EndOfDay(new DateTime(now.AddMonths(-1).Year, today.AddMonths(-1).Month, DateTime.DaysInMonth(now.Year, today.AddMonths(-1).Month)));    

    var query = from item in context.Invoices.AsNoTracking() 
       where (item.InvoiceDate >= date12monthago && item.InvoiceDate <= today) 
       && (item.InvType == "I" || item.InvType == null) 
       select item; 

    if (manufacturerId.HasValue && manufacturerId.Value > 0) 
     query=query.Where(item=>item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Any(m=>m.ManufacturerId==manufacturerId)); 

    if (regionId.HasValue && regionId.Value > 0) 
     query=query.Where(item=>item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Any(m=>m.RegionId==regionId.Value)); 

    if (vehicleTypeId.HasValue && vehicleTypeId.Value > 0) 
     query=query.Where(item=>item.Repair.Job.Vehicle.Model.VehicleTypes.Any(v=>v.Id==vehicleTypeId.Value)); 

    var query2 = from item in query 
       group item by new { item.InvoiceDate.Month, item.Repair.Job.Bodyshop } into g 
       select new TReport 
       { 
        BodyshopId = g.Key.Bodyshop.Id, 
        Month = g.Key.Month, 
        MonthAllJobTotal = g.Count() 
       }; 

    return query2; 
} 

Тогда я бы разбить их на части и преобразовать их в методы расширения:

public static class MyExtensions 
{ 
    public static IQueryable<Invoice> Recent(this IQueryable<Invoice> context,long? manufacturerId=null,long? regionId=null,long? vehicleId=null) 
    { 
     var now = DateTime.Now; 
     var today = new DateTime(now.Year, now.Month, 1); 
     var date1monthago = today.AddMonths(-1); 
     var date2monthago = today.AddMonths(-2); 
     var date3monthago = today.AddMonths(-3); 
     var date4monthago = today.AddMonths(-4); 
     var date5monthago = today.AddMonths(-5); 
     var date6monthago = today.AddMonths(-6); 
     today = TimeManager.EndOfDay(new DateTime(now.AddMonths(-1).Year, today.AddMonths(-1).Month, DateTime.DaysInMonth(now.Year, today.AddMonths(-1).Month)));    

     var query = from item in context.Invoices.AsNoTracking() 
       where (item.InvoiceDate >= date12monthago && item.InvoiceDate <= today) 
       && (item.InvType == "I" || item.InvType == null) 
       select item; 

     if (manufacturerId.HasValue && manufacturerId.Value > 0) 
      query=query.Where(item=>item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Any(m=>m.ManufacturerId==manufacturerId)); 

     if (regionId.HasValue && regionId.Value > 0) 
      query=query.Where(item=>item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Any(m=>m.RegionId==regionId.Value)); 

     if (vehicleTypeId.HasValue && vehicleTypeId.Value > 0) 
      query=query.Where(item=>item.Repair.Job.Vehicle.Model.VehicleTypes.Any(v=>v.Id==vehicleTypeId.Value)); 
    return query; 
} 
public static IQueryable<Report> ToReport(this IQueryable<Invoice> context) 
{ 
    return (from item in query 
       group item by new { item.InvoiceDate.Month, item.Repair.Job.Bodyshop } into g 
       select new TReport 
       { 
        BodyshopId = g.Key.Bodyshop.Id, 
        Month = g.Key.Month, 
        MonthAllJobTotal = g.Count() 
       }); 

} 
} 

Теперь вы можете сделать следующее:

var reports=db.Invoices.Recent.ToReport(); 

или

var reports=db.Invoices.Recent(ManufacturerEnum.Toyota).ToReport(); 
3

Вы можете реализовать пейджинг, чтобы не материализовать все результаты. Я имею в виду, вы могли бы реализовать методы Skip и Take linq.

Простой пример, основанный на коде:

public IList<Report> GetReport(CmsEntities context, long manufacturerId, long? regionId, long? vehicleTypeId, int pageSize, int currentPage) 
     { 

     //Code removed to simplify 

     return query2.Skip(pageSize * currentPage).Take(pageSize); 

     } 
+0

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

3

Во-первых, некоторые общие советы по оптимизации SQL:

Прежде чем пытаться сделать оптимизацию, вы должны всегда профиль. Профилирование имеет комбинированное преимущество, дающее вам объективное описание или где вы стоите с точки зрения производительности и даете советы о том, где следует начинать ваши усилия по оптимизации. И в качестве бесплатной дополнительной выгоды, в конце концов, вы можете оправдать свою тяжелую работу своему руководству с четкими объективными номерами (или даже графиками производительности для любителей Powerpoint).

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

  • план Оптимизация SQL (может быть трудоемкой задачей времени)

    1. Выяснить, что запрос в SQL ,
    2. Запустите запрос в студии управления SQL или аналогичном инструменте, чтобы определить наилучший возможный план выполнения (возможно, вам потребуется добавить индексы в процесс). Вы должны знать оттуда, если оптимизации будет достаточно, чтобы соответствовать вашим критериям эффективности или нет.
    3. Изучите текущий план выполнения, чтобы проверить, используют ли текущий запрос наиболее оптимальные индексы/ключи, объединения.
    4. Либо модифицируйте код, либо используйте промежуточный объект SQL (хранимую процедуру или представление), чтобы сделать код linq для sql использующим оптимизированный план выполнения.
  • Оптимизировать путь обрабатывать данные коды (менее трудоемкая задача)

    1. Caching (это может быть несколькими способами, но принцип должен принести вам данные и обрабатывать его в коде). Это особенно эффективно, когда оптимизатор запросов не может эффективно управлять вашим запросом (старая версия базы данных/движок не очень хороша).
    2. Компиляция Ссылка на запрос SQL. Joe Albahari explains better than me.

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

С точки зрения сложности вашего кода linq на sql, есть вероятность, что сгенерированный SQL-запрос является субоптимальным. С другой стороны, linq в управлении объектами памяти довольно быстро по сравнению с I/O с удаленным SQL db.

Обратите внимание, что если вы не выполняете кеширование запросов, он будет извлекать данные каждый раз. Кэширование можно было бы сделать так:

var allJobs = query.Where(x => x.Repair.Job.BodyshopId == g.Key.Bodyshop.Id).ToArray(); 

Как общее правило, K EEP I т S реали S tupid и ваш запрос не является. Вы можете реорганизовать его немного, как это кажется, что есть совершенно излишнее код:

MonthSixManufJobTotal = manufJobs.Where(x => x.InvoiceDate.Month == date6monthago.Month && x.InvoiceDate.Year == date6monthago.Year).GroupBy(x => x.Repair.Job).Count(), 
    MonthSixJobTotal = allJobs.Where(x => x.InvoiceDate.Month == date6monthago.Month && x.InvoiceDate.Year == date6monthago.Year).GroupBy(x => x.Repair.Job).Count(), 

Существует довольно распространенное мнение о том, что, группируя весь код в одном методе (аналогичным образом, как на ассемблере), вы ограничить количество вызовов методов, а затем лучше работать с точки зрения производительности. Однако истина противоречит интуиции. Используя правило divide и conquer, метод chunked code действительно будет лучше работать в конце, потому что его будет легче поддерживать, оптимизировать, улучшать, рефакторировать. Это была цель эволюции программного обеспечения от ассемблера до C#.

+2

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

0

(Примечание: Я основывая это на запросы в вашем редактировать)

Есть пара вещей, которые вы могли бы потенциально попробовать. Во-первых, вы можете упростить использование .Select() с .Contains() вместо .Any(). Этот может привести к более простому запросу, генерируемому EF, хотя его трудно определить без использования SQL Server Profiler или Visual Studio Debugger для просмотра реального SQL-запроса. Например, изменить .Select(x => x.ManufacturerId).Contains(manufacturerId) на .Any(x => x.ManufacturerId == manufacturerId).

Второе, что нужно попробовать, - это выполнить первый запрос с SQL Server и обработать второй запрос в приложении. EF defers execution запроса до тех пор, пока не произойдет перечисление результата (например, .ToList() или foreach). Таким образом, вы можете попробовать (from item in context ... select item).ToList() по первому запросу, который приведет к выполнению второго запроса в приложении, а не на SQL Server. Это помогло бы, если group by во втором запросе приведет к ухудшению производительности, если это будет сделано SQL Server, а не в самом приложении.

Однако, если вы пытаетесь второе предложение может быть отрицательный эффект вызван item.Repair.Job.Bodyshop группировки, если это свойство virtual навигации, потому что EF будет получить этот объект в отдельности (в отличие от всех в одном запросе). Это можно смягчить, изменив первый запрос на context.Invoices.AsNoTracking().Include("Repair.Job") или context.Invoices.AsNoTracking().Include(x => x.Repair.Job) (второй вариант недоступен старым версиям EF) и сменил второй запрос на group item by new { item.InvoiceDate.Month, item.Repair.Job.BodyshopId }.

+0

Я думаю, что ваш первый момент необходим. Другой момент меньше, потому что группировка не переводится в SQL 'group by', а только в сортировку (фактическая группировка уже выполняется в памяти). «Include» здесь не применяется, потому что результат - это проекция. Другое дело, что 'regionId.Value> 0?' И т. Д.может быть преобразован в 'if (regionId.Value> 0) query = query.Where (item => item.Repair.Job ....', что уменьшает количество решений, которые должны быть приняты движком db. –

+0

@GertArnold Последняя часть этого [blog] (https://coding.abel.nu/2012/07/always-check-generated-sql-ef/), по-видимому, подразумевает, что существуют сценарии, в которых будет генерироваться поставщик SQL Server для EF SQL-запросы для группировки, а не выполнение этого в приложении. Я не уверен, что эти два запроса не объединены и выполняются на сервере SQL, когда нет нумерации между запросами, использующими '.ToList()'. вторая точка, условная цепочка в предложениях .Where() 'только по мере необходимости должна помогать генерации запросов. Профилирование сгенерированного SQL было бы единственным способом узнать наверняка. – jrivor

0

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

В расширения ToDictionary и ToLookup вы можете получить много удовольствия.

Бьюсь об заклад, если вы взглянули на SQL, который вы отправляете в базу данных, это будет безумие.

0

Наряду с устранением ненужных where условий, я положу мою ставку на пункте group by.

Вы ввели item.Repair.Job.Bodyshop в качестве одного из полей группировки. В любое время, когда вы используете что-то вроде этого, EF будет генерировать предложение SQL GROUP BY, включая все поля из соответствующей таблицы. Я не знаю, сколько столбцов у вас есть в таблице db, которая соответствует вашему объекту Bodyshop, но в любом случае использование этого способа, скорее всего, не позволит создать хороший план выполнения SQL.

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

var query = context.Invoices.AsNoTracking().Where(item => 
    (item.InvType == "I" || item.InvType == null) && 
    (item.InvoiceDate >= date12monthago && item.InvoiceDate <= today)); 

if (regionId.HasValue && regionId.Value > 0) 
    query = query.Where(item => 
     item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Any(source => 
      source.ManufacturerId == manufacturerId && source.RegionId == regionId.Value)); 
else 
    query = query.Where(item => 
     item.Repair.Job.Bodyshop.Manufacturer2Bodyshop.Any(source => 
      source.ManufacturerId == manufacturerId)); 

if (vehicleTypeId.HasValue && vehicleTypeId.Value > 0) 
    query = query.Where(item => 
     item.Repair.Job.Vehicle.Model.VehicleTypes.Any(vehicleType => 
      vehicleType.Id == vehicleTypeId.Value); 

var query2 = query 
    .GroupBy(item => new { Month = item.InvoiceDate.Month, BodyshopId = item.Repair.Job.Bodyshop.Id }) 
    .Select(g => new TReport { BodyshopId = g.Key.BodyshopId, Month = g.Key.Month, MonthAllJobTotal = g.Count() }); 

var result = query2.ToList();