Aggregation and statistical queries

Sum, Average, Min and Max throw on an empty sequence of non-nullable values, and the aggregate operators are the ones that surprise people in production.

The standard aggregates

var orders = Array.Empty<Order>();

// These throw InvalidOperationException on an empty sequence
// var total = orders.Sum(o => o.Total);
// var avg = orders.Average(o => o.Total);
// var max = orders.Max(o => o.Total);

// Safe forms
var total = orders.Sum(o => (decimal?)o.Total) ?? 0m;
var avg = orders.Average(o => (decimal?)o.Total);          // null when empty
var max = orders.Select(o => (decimal?)o.Total).Max();     // null when empty

// Count versus LongCount: Count() is an int and overflows past 2.1 billion
var count = orders.LongCount();

// Any is cheaper than Count for an existence test: it can stop at the first hit
var hasAny = orders.Any();
var hasOpen = orders.Any(o => o.Status == OrderStatus.Open);

// MinBy and MaxBy return the element, not the key
var newest = orders.MaxBy(o => o.PlacedAt);
var cheapest = orders.MinBy(o => o.Total);

// Aggregate is the general fold
var csv = orders.Aggregate(
    new System.Text.StringBuilder(),
    (sb, o) => sb.Append(o.Id).Append(',')).ToString();

var runningTotal = orders.Aggregate(
    seed: 0m,
    func: (acc, o) => acc + o.Total,
    resultSelector: acc => Math.Round(acc, 2));
  • Sum, Average, Min and Max throw on an empty sequence when the element type is a non-nullable value type. Cast the selector to a nullable to get a null instead.
  • Empty Min and Max on a reference type return null rather than throwing, which is an inconsistency worth remembering.
  • Count() on an IEnumerable that is not a collection enumerates the whole thing. On an ICollection it uses the Count property, which is free.
  • For a decimal average, do the division yourself if you care about rounding: Average uses the type's own arithmetic.

Aggregation over groups

// One pass instead of one query per group
var report = orders
    .GroupBy(o => o.Currency)
    .Select(g => new
    {
        Currency = g.Key,
        Count = g.Count(),
        Total = g.Sum(o => o.Total),
        Average = g.Average(o => o.Total),
        Largest = g.Max(o => o.Total),
        First = g.MinBy(o => o.PlacedAt)!.PlacedAt,
    })
    .OrderByDescending(x => x.Total)
    .ToList();

// A percentile, computed from the ordered values
decimal Percentile(IEnumerable<decimal> values, double p)
{
    var sorted = values.OrderBy(v => v).ToArray();
    if (sorted.Length == 0) return 0m;

    var rank = (p / 100.0) * (sorted.Length - 1);
    var low = (int)Math.Floor(rank);
    var high = (int)Math.Ceiling(rank);
    if (low == high) return sorted[low];

    var weight = (decimal)(rank - low);
    return sorted[low] * (1 - weight) + sorted[high] * weight;
}

// What EF Core can translate: Count, Sum, Average, Min, Max per group
var perDay = await db.Orders
    .Where(o => o.PlacedAt >= since)
    .GroupBy(o => o.PlacedAt.Date)
    .Select(g => new { Day = g.Key, Count = g.Count(), Total = g.Sum(o => o.Total) })
    .ToListAsync(ct);

// A conditional aggregate, which the provider translates to a filtered count
var stats = await db.Orders
    .GroupBy(o => o.Currency)
    .Select(g => new
    {
        g.Key,
        Open = g.Count(o => o.Status == OrderStatus.Open),
        Fulfilled = g.Count(o => o.Status == OrderStatus.Fulfilled),
    })
    .ToListAsync(ct);
⚠️
A custom aggregate from a library, such as a percentile or a standard deviation, will not translate to SQL. Either compute it in the database with a raw query, or fetch only the values you need and aggregate in memory — never fetch whole entities to run one arithmetic function.

FAQ

How do I sum a nullable decimal without a cast?
If the property is already decimal?, Sum returns decimal? and gives null on an empty sequence. The cast is only needed when the source property is a non-nullable decimal.
Why is Count() slow on my query?
Because it may be enumerating the whole sequence, and on a database it issues a second round trip. If you only need to know whether anything exists, use Any.

Common LINQ operators Grouping, joining and set operations

Last refreshed 2026-09-18.