Как сгруппировать по неделям в Entity Framework Core?

В Entity Framework 6 я могу использовать метод SqlFunctions.DatePart():

var byWeek = data.GroupBy(x => SqlFunctions.DatePart("week", x.Date));

Но эти классы (DbFunctions и SqlFunctions недоступны в Entity Framework Core) (ссылка).

Итак, мой вопрос: Как я могу сгруппировать по неделям в ядре Entity Framework?


person Nikolay Kostov    schedule 08.03.2017    source источник
comment
Поскольку в настоящее время все запросы EF Core с предложением GroupBy обрабатываются в памяти, вы можете безопасно использовать x.Date.DayOfYear / 7 или что-то в этом роде.   -  person Ivan Stoev    schedule 08.03.2017
comment
@IvanStoev больше нет. групповые запросы компилируются в sql, начиная с версии ef core 2.1. см. мой ответ для рабочего примера.   -  person cyptus    schedule 10.06.2019
comment
Примечание. Всем, кто найдет это, убедитесь, что вы понимаете разницу между week и iso_week и что вы хотите. Год недели iso не всегда совпадает с календарным годом (например, в 2020 году неделя 1 началась 30 декабря 2010 года). en.wikipedia.org/wiki/ISO_week_date   -  person Simon_Weaver    schedule 15.03.2021


Ответы (3)


Мой текущий обходной путь для отсутствующей функциональности:

var firstMondayOfYear = this.GetFirstMondayOfYear(DateTime.Now.Year);
var entries =
    this.entitiesService.FindForLastMonths(this.CurrentUser.Id, 6)
        .GroupBy(x => ((int)(x.Date - firstMondayOfYear).TotalDays / 7))

Функция GetFirstMondayOfYear:

private DateTime GetFirstMondayOfYear(int year)
{
    var dt = new DateTime(year, 1, 1);
    while (dt.DayOfWeek != DayOfWeek.Monday)
    {
        dt = dt.AddDays(1);
    }

    return dt;
}

Эта группировка дает номер недели для текущего года и отрицательные значения для предыдущих лет.

Позже вы можете получить название недели, используя этот геттер:

public string WeekName
{
    get
    {
        var year = DateTime.Now.AddYears((int)Math.Floor(this.WeekNumber / 52.0)).Year;
        var weekNumber = this.WeekNumber % 52;
        while (weekNumber < 0)
        {
            weekNumber += 52;
        }

        return $"{year}, W{weekNumber}";
    }
}
person Nikolay Kostov    schedule 08.03.2017
comment
Обратите внимание, что весь этот запрос будет выполняться в памяти, поэтому, вероятно, будет очень медленным. - person DavidG; 12.06.2019

Можно использовать функцию SQL datepart, обернув ее атрибутом DbFunctionAttribute. Сложность заключается в том, чтобы указать ef core не обрабатывать параметр типа datepart как строку. Пример:

Дбконтекст:

public int? DatePart(string datePartArg, DateTime? date) => throw new Exception();

public void OnModelCreating(DbModelBuilder modelBuilder) {
    var methodInfo = typeof(DbContext).GetRuntimeMethod(nameof(DatePart), new[] { typeof(string), typeof(DateTime) });
    modelBuilder
        .HasDbFunction(methodInfo)
        .HasTranslation(args => new SqlFunctionExpression(nameof(DatePart), typeof(int?), new[]
                {
                        new SqlFragmentExpression(args.ToArray()[0].ToString()),
                        args.ToArray()[1]
                }));
}

Запрос:

repository.GroupBy(x => dbContext.DatePart("week", x.CreatedAt));

дополнительная информация: https://github.com/aspnet/EntityFrameworkCore/issues/10404

person cyptus    schedule 07.06.2019

У меня уже было представление, поэтому я просто добавил в это представление дополнительные столбцы, представляющие неделю, месяц и год, — для удобной группировки с помощью EF Core.

OrderDateDt,
DATEPART(week, OrderDateDt) OrderDateDt_Week,
DATEPART(month, OrderDateDt) OrderDateDt_Month,
DATEPART(year, OrderDateDt) OrderDateDt_Year,
person Simon_Weaver    schedule 29.12.2019
comment
Обратите внимание, что datepart(week) нельзя использовать для индексированного представления, но я думаю, что datepart(iso_week) можно. - person Simon_Weaver; 30.12.2019
comment
также, поскольку я ответил на этот вопрос, я узнал об ISO_WEEK больше, чем хотел, поэтому обязательно изучите, что соответствует вашим потребностям. Кроме того, ISO_WEEK на сегодняшний день (30.12.19) на самом деле равен 1, поскольку он является частью первой недели следующего года, поэтому вы не можете использовать year, иначе 30.12.19 станет первой неделей 2019 года. что не правильно! - person Simon_Weaver; 30.12.2019