0

How do I reuse LINQ aggregation queries on a set of generic C# types with a common shape or interface that is known at compile-time without resorting to black magic dynamic ExpressionBuilder stuff?

For example,

// Each "reportable" class has a decimal property to sum over:
interface IReportable<T> {
    Expression<T, decimal> GetSummingExpr(); // no static interface methods :(
}

public static class ReportingExtensions {
    public static IQueryable<decimal> Report<T>(this IQueryable<T> qry)
    {
        return x => x.Sum(qry.GetSummingExpr()); // unsure if interfaces can do this
    }
}

var invoices = new Invoice [] { Invoice(100.0M), ... };
var quotes = new Quote [] { Quote(50.0M), ... };

// Ideal use-cases:
var invoiceReport = invoices.AsQueryable().Report();
var quoteReport = quotes.AsQueryable().Report();

// "Reportable" class implementations:
public class Invoice : IReportable<Invoice>
{
    public decimal InvoiceTotal { get; set; }

    public Expression<Func<Invoice, decimal>> GetTotalExpr()
    {
        return x => x.InvoiceTotal;
    }
}

public class Quote : IReportable<Quote>
{
    public decimal QuoteTotal { get; set; }

    public Expression<Func<Quote, decimal>> GetTotalExpr()
    {
        return x => x.QuoteTotal;
    }
}

I have tried various approaches, but I can't get it to work without introducing magic strings, ala x.GroupBy("InvoiceTotal").

To my limited knowledge, this stuff is hard because LINQ types are resolved before generics are "rendered" to "concrete types". However, all of the type signatures are known at compile-time, so it seems like something I should be able to coerce the compiler to do.

Petrus Theron
  • 27,855
  • 36
  • 153
  • 287
  • why do you need your interface to be `Expression>`? Can it simply define a common property like: `interface IReportable { decimal ReportTotal {get;}}` – Pedro Feb 10 '17 at 18:36
  • The interface is useless if you always need knowledge of the implementing type to actually use it. It's only useful if the property to sum over is part of the interface. – Gert Arnold Feb 10 '17 at 19:56
  • Pedro: I could be wrong, but to my knowledge, a getter implementation is eager. It maps a value instead of projecting an underlying property for efficient database querying. – Petrus Theron Feb 13 '17 at 14:28

0 Answers0