0

I have code very much like this:

Context.History.GroupBy(d => new { d.Category, d.Month })
    .Select(group => new
    {
        Category = group.Key.Category,
        Month = group.Key.Month,
        // Geometric mean:
        Value = Math.Exp(group.Average(a => Math.Log(a.Value)))
    })
    .GroupBy(d => d.Month)
    .Select(monthly => new Dto {
         Month = monthly.Month,
         Average = monthly.Average(a => a.Value)
    });

There's a few other places I calculate the geometric mean, so I would like to put that in an extension method, so the line after the comment would look like Value = group.GeometricMean(a => a.Value).

  • If I write a regular extension method on IEnumerable<T>, Entity Framework complains that it can't convert it to a query.

  • If I write a method operating on IQueryable<T>, I'm informed that group is, in fact, not an IQueryable<T>, but rather an IGrouping<TKey, T>.

  • Trying to call it as Value = group.AsQueryable().GeometricMean(a => a.Value) leads to an ArgumentException saying 'Expression of type IGrouping cannot be used for parameter of type IQueryable (Parameter arg0)'.

It seems to me I must somehow turn that IGrouping into IQueryable for my extension method to work, but I can't figure out how. There's a few questions on SO that wander near that subject, but none that I've found that are applicable here. It might be that this is simply not supported in EF as of now.

More information: replacing the whole Select body with some function is a no-go, as the point is to make the geometric mean calculation reusable - the rest is simply not reusable. It needs to be done in SQL, as it's been shown to be far too slow to fetch the data and do the calculation in C#.

Looking at the source of some IQueryable extension methods, it seems the body of the extension method would build up some expression tree and pass that to source.Provider.Execute. I think I may already have managed that part, but I've been unable to properly test it due to the above issues.

BioTronic
  • 2,279
  • 13
  • 15
  • As I know, custom aggregates are not supported yet: https://github.com/dotnet/efcore/issues/22957 – Svyatoslav Danyliv Mar 04 '21 at 09:58
  • Not the answer I wanted, but at least now I know. Thanks a lot! – BioTronic Mar 04 '21 at 10:01
  • Before you give up, there Is extension which supports custom aggregates https://github.com/linq2db/linq2db.EntityFrameworkCore – Svyatoslav Danyliv Mar 04 '21 at 10:04
  • That's not a custom aggregate afaik. Isn't Average supported by EF Core? Also Math.Exp and Math.Log are translatble too, so shouldn't be a problem – dglozano Mar 04 '21 at 11:36
  • Can you post the code for the extension GeometricMean that you wrote? – dglozano Mar 04 '21 at 11:38
  • I've written many variations. Here's two: https://gist.github.com/Biotronic/55bab00cc6f9bb52e5d850088c93077a – BioTronic Mar 04 '21 at 11:58
  • @BioTronic is it possible to define the extension on IGrouping instead of IQueryable? – dglozano Mar 04 '21 at 14:11
  • If I do, it's suddenly an opaque function call rather than an `Expression` which EF is able to operate on. I mean, it seems to be that anyway, but I was hoping the call to `source.Provider.Execute` would give it some magical access. – BioTronic Mar 04 '21 at 14:35
  • Guys, you can do any magic. If translator do not support custom aggregates natively - you will fail. I know complexity. – Svyatoslav Danyliv Mar 04 '21 at 16:09

0 Answers0