Entity Framework: Эффективная группировка по месяцам - PullRequest
11 голосов
/ 27 марта 2012

Я провел небольшое исследование по этому вопросу, и лучшее, что я нашел на данный момент, это использование Asenumerable для всего набора данных, чтобы фильтрация происходила в linq с объектами, а не с БД. Я использую последнюю версию EF.

Мой рабочий (но очень медленный) код:

        var trendData = 
            from d in ExpenseItemsViewableDirect.AsEnumerable()
            group d by new {Period = d.Er_Approved_Date.Year.ToString() + "-" + d.Er_Approved_Date.Month.ToString("00") } into g
            select new
            {
                Period = g.Key.Period,
                Total = g.Sum(x => x.Item_Amount),
                AveragePerTrans = Math.Round(g.Average(x => x.Item_Amount),2)
            };

Это дает мне месяцы в формате ГГГГ-ММ вместе с общей суммой и средней суммой. Однако каждый раз это занимает несколько минут.

Мой другой обходной путь - выполнить запрос на обновление в SQL, поэтому у меня есть поле YYYYMM, с помощью которого можно сгруппировать исходно. Смена БД, однако, не является легким решением, поэтому любые предложения будут оценены.

В потоке, в котором я нашел приведенную выше идею кода (/1373027/gruppirovka-po-nedelyam-v-linq-to-entities) упоминается «ожидание до .NET 4.0». Есть ли что-нибудь недавно появившееся, что поможет в этой ситуации?

Ответы [ 3 ]

16 голосов
/ 27 марта 2012

Причиной низкой производительности является то, что вся таблица извлекается в память (AsEnumerable ()). Затем вы можете группировать по годам и месяцам, как это

var trendData = 
            (from d in ExpenseItemsViewableDirect
            group d by new {
                            Year = d.Er_Approved_Date.Year, 
                            Month = d.Er_Approved_Date.Month 
                            } into g
            select new
            {
                Year = g.Key.Year,
                Month = g.Key.Month,
                Total = g.Sum(x => x.Item_Amount),
                AveragePerTrans = Math.Round(g.Average(x => x.Item_Amount),2)
            }
       ).AsEnumerable()
        .Select(g=>new {
              Period = g.Year + "-" + g.Month,
              Total = g.Total,
               AveragePerTrans = g.AveragePerTrans
         });

редактировать

Исходный запрос, исходя из моего ответа, пытался выполнить конкатенацию между int и строкой, которая не переводится EF в операторы SQL. Я мог бы использовать SqlFunctions класс, но запрос он выглядит довольно уродливо. Поэтому я добавил AsEnumerable () после группировки , что означает, что EF выполнит групповой запрос на сервере, получит год, месяц и т. Д., Но пользовательский прогноз сделано поверх объектов (что следует после AsEnumerable ()).

9 голосов
/ 13 октября 2014

Когда речь идет о группировании по месяцам, я предпочитаю выполнять эту задачу следующим образом:

var sqlMinDate = (DateTime) SqlDateTime.MinValue;

var trendData = ExpenseItemsViewableDirect
    .GroupBy(x => SqlFunctions.DateAdd("month", SqlFunctions.DateDiff("month", sqlMinDate, x.Er_Approved_Date), sqlMinDate))
    .Select(x => new
    {
        Period = g.Key // DateTime type
    })

Так как тип даты и времени сохраняется в результате группировки.

2 голосов
/ 16 декабря 2015

Подобно тому, что написал Cryss, я делаю следующее для EF. Обратите внимание, что мы должны использовать EntityFunctions для возможности вызова всех поставщиков БД, поддерживаемых EF. SqlFunctions работает только для SQLServer.

var sqlMinDate = (DateTime) SqlDateTime.MinValue; 

(from x in ExpenseItemsViewableDirect
let month = EntityFunctions.AddMonths(sqlMinDate, EntityFunctions.DiffMonths(sqlMinDate, x.Er_Approved_Date))
group d by month 
into g
select new
{
Period = g.Key,
   Total = g.Sum(x => x.Item_Amount),
   AveragePerTrans = Math.Round(g.Average(x => x.Item_Amount),2)
}).Dump();

Вкус сгенерированного SQL (из аналогичной схемы):

-- Region Parameters
DECLARE @p__linq__0 DateTime2 = '1753-01-01 00:00:00.0000000'
DECLARE @p__linq__1 DateTime2 = '1753-01-01 00:00:00.0000000'
-- EndRegion
SELECT 
1 AS [C1], 
[GroupBy1].[K1] AS [C2], 
[GroupBy1].[A1] AS [C3]
FROM ( SELECT 
    [Project1].[C1] AS [K1], 
    FROM ( SELECT 
        DATEADD (month, DATEDIFF (month, @p__linq__1, [Extent1].[CreationDate]), @p__linq__0) AS [C1]
        FROM [YourTable] AS [Extent1]
    )  AS [Project1]
    GROUP BY [Project1].[C1]
)  AS [GroupBy1]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...