Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EFCore 2.2 GroupBy Sum and DateDiff

I'm trying to translate the following SQL in to an EF Core query and I'm getting warnings that GroupBy and Sum will be evaluated locally. Is there anyway currently to write this that it will fully translate to SQL?

SELECT UserId, ST.StatusId, SUM(DATEDIFF(MINUTE, StartDate, ISNULL(EndDate,GETDATE()))) AS Time
FROM StatusTransaction ST
WHERE
    TeamManagerId = 1
    AND StartDate >= N'01-01-2019'
    AND ISNULL(EndDate,GETDATE()) <= N'01-02-2019'
GROUP BY UserId, ST.StatusId
ORDER BY UserId

And these are the EF queries I've used:

var efFunction = await context
    .Where(st => st.TeamManagerId == tmId && st.StartDate >= dateFrom && (st.EndDate ?? DateTime.Now) <= dateTo)
    .GroupBy(st => new { st.UserId, st.StatusId })
    .Select(g => new
        {
            g.Key.UserId,
            g.Key.StatusId,
            Time = g.Sum(st => Microsoft.EntityFrameworkCore.EF.Functions.DateDiffMinute(st.StartDate, st.EndDate)) // null check not done on end date - (st.EndDate ?? DateTime.Now) causes an error here
        }).ToListAsync(cancellationToken).ConfigureAwait(false);

var simpleDateSubtraction = await context
    .Where(st => st.TeamManagerId == tmId && st.StartDate >= dateFrom && (st.EndDate ?? DateTime.Now) <= dateTo)
    .GroupBy(st => new { st.UserId, st.StatusId })
    .Select(g => new
    {
        g.Key.UserId,
        g.Key.StatusId,
        Time = g.Sum(st => st.EndDate.Value.Subtract(st.StartDate).Minutes)// null check not done on end date - (st.EndDate ?? DateTime.Now) causes an error here
    }).ToListAsync(cancellationToken).ConfigureAwait(false);

var groupBySimpleSum = await context
    .Where(st => st.TeamManagerId == tmId)
    .GroupBy(st => new { st.TeamManagerId, st.OperationsManagerId })
    .Select(g => new
    {
        g.Key.OperationsManagerId,
        g.Key.TeamManagerId,
        Foo = g.Sum(st => st.UserId) // nonsense but a simple column to sum, this translates fully to SQL
    }).ToListAsync(cancellationToken).ConfigureAwait(false);
like image 338
lanky393 Avatar asked Feb 13 '19 12:02

lanky393


People also ask

Is the groupby operator evaluated in memory in EF Core?

Before version 2.1, in EF Core the GroupBy LINQ operator would always be evaluated in memory. We now support translating it to the SQL GROUP BY clause in most common cases. There is nothing you can do in previous EF Core versions.

How to improve LINQ groupby translation in EF Core?

By default, EF Core will log a warning when client evaluation is performed. See here .Try to use RawSQL if it is inefficient. Show activity on this post. If possible, upgrade to EF Core 2.1 (or 2.2) in order to get improved LINQ GroupBy translation. Before version 2.1, in EF Core the GroupBy LINQ operator would always be evaluated in memory.

What is efef core trying to do with the data?

EF Core 2.2 is trying to bring all the data locally. **Here is the query getting generated** SELECT [adjDetail]. [QuotaId], [adjDetail].

How to call datediffday from a table in Entity Framework?

You would use EntityFunctions or DbFunctions DiffDays method. Using DbFunctions, accessed via EF.Functions, you can call DateDiffDay: var ans = from t in Table1 group t by 1 into tg select tg.Sum (r => EF.Functions.DateDiffDay (r.FromDate, r.ToDate)); My SQL to LINQ Recipe might help you with some translation issues in the future.


1 Answers

First, EF Core still doesn't support translating TimeSpan operations, and DateTime difference produces TimeSpan, hence EF.Functions.DateDiff methods are the right way to go.

Second, it still can translate GroupBy aggregates only on simple member accessor expressions. So you have to either pre Select the GroupBy expressions:

var query = context
    .Where(st => st.TeamManagerId == tmId
        && st.StartDate >= dateFrom
        && (st.EndDate ?? DateTime.Now) <= dateTo
    )
    .Select(st => new
    {
        st.UserId,
        st.StatusId,
        Time = EF.Functions.DateDiffMinute(st.StartDate, st.EndDate ?? DateTime.Now)
    })
    .GroupBy(st => new { st.UserId, st.StatusId })
    .Select(g => new
    {
        g.Key.UserId,
        g.Key.StatusId,
        Time = g.Sum(st => st.Time)
    });

or use the GroupBy overload which allows pre selecting the source for the aggregates:

var query = context
    .Where(st => st.TeamManagerId == tmId
        && st.StartDate >= dateFrom
        && (st.EndDate ?? DateTime.Now) <= dateTo
    )
    .GroupBy(st => new { st.UserId, st.StatusId }, st => new
    {
        Time = EF.Functions.DateDiffMinute(st.StartDate, st.EndDate ?? DateTime.Now)
    })
    .Select(g => new
    {
        g.Key.UserId,
        g.Key.StatusId,
        Time = g.Sum(st => st.Time)
    });
like image 115
Ivan Stoev Avatar answered Sep 25 '22 21:09

Ivan Stoev