Table of Contents

Query transformation

The GetTransformedListAsync method allows you to execute arbitrary LINQ projections — groupings, aggregations, distinct selections, custom projections — against the data source.

Overview

Unlike GetListAsync, these methods accept a transformation delegate that reshapes the query result into any shape (anonymous type, grouped result, scalar collection, etc.). In the nominal case, the transformation is translated directly to a single server-side query (SQL) with no intermediate rows loaded into memory. In some scenarios — for example when retrieving rules provide in-memory items — the transformation may run on already-materialized data.

Tip

When both queryCustomizationBeforeTransformation and queryTransformation are provided, the pre-filtering delegate is applied first. Use it to narrow down the dataset (e.g. a Where clause) before a heavier operation like GroupBy is evaluated. In the nominal case both delegates are combined into a single server-side query.

GetTransformedListAsync

Signature Description
GetTransformedListAsync(queryTransformation) Simplest form — transformation only.
GetTransformedListAsync(queryTransformation, cancellationToken) Same, with cancellation support.
GetTransformedListAsync(queryTransformation, queryCustomizationBeforeTransformation) Pre-filter rows before transformation.
GetTransformedListAsync(queryTransformation, queryCustomizationBeforeTransformation, cancellationToken) Full control: pre-filter + transformation + cancellation.

Examples

Count orders per customer

IReadOnlyList<object> results = await _orderViewRepository.GetTransformedListAsync(
    q => q.GroupBy(o => o.CustomerId)
          .Select(g => new { CustomerId = g.Key, OrderCount = g.Count() }));

foreach (dynamic row in results)
{
    Console.WriteLine($"Customer {row.CustomerId}: {row.OrderCount} orders");
}

Total revenue per product category

IReadOnlyList<object> revenues = await _orderLineViewRepository.GetTransformedListAsync(
    q => q.GroupBy(l => l.CategoryName)
          .Select(g => new { Category = g.Key, TotalRevenue = g.Sum(l => l.UnitPrice * l.Quantity) }));

Revenue per category filtered by year

int year = 2025;
IReadOnlyList<object> revenues = await _orderLineViewRepository.GetTransformedListAsync(
    queryTransformation: q =>
        q.GroupBy(l => l.CategoryName)
         .Select(g => new { Category = g.Key, TotalRevenue = g.Sum(l => l.UnitPrice * l.Quantity) }),
    queryCustomizationBeforeTransformation: q =>
        q.Where(l => l.OrderYear == year));

Active employee headcount per department (ordered)

IReadOnlyList<object> headcount = await _employeeViewRepository.GetTransformedListAsync(
    queryTransformation: q =>
        q.GroupBy(e => e.DepartmentName)
         .Select(g => new { Department = g.Key, Count = g.Count() })
         .OrderByDescending(r => r.Count),
    queryCustomizationBeforeTransformation: q =>
        q.Where(e => e.IsActive));

Top 5 products by quantity sold

IReadOnlyList<object> top5 = await _orderLineViewRepository.GetTransformedListAsync(
    queryTransformation: q =>
        q.GroupBy(l => l.ProductName)
         .Select(g => new { Product = g.Key, TotalQty = g.Sum(l => l.Quantity) })
         .OrderByDescending(r => r.TotalQty)
         .Take(5),
    queryCustomizationBeforeTransformation: null,
    cancellationToken: cancellationToken);

Invoice summary per salesperson for Q1

IReadOnlyList<object> summaries = await _invoiceViewRepository.GetTransformedListAsync(
    queryTransformation: q =>
        q.GroupBy(i => i.SalespersonName)
         .Select(g => new
         {
             Salesperson  = g.Key,
             InvoiceCount = g.Count(),
             TotalAmount  = g.Sum(i => i.TotalAmount),
         }),
    queryCustomizationBeforeTransformation: q =>
        q.Where(i => i.InvoiceDate.Year == 2025 && i.InvoiceDate.Month <= 3),
    cancellationToken: cancellationToken);

Using a data object for strong typing

Instead of projecting to an anonymous type (which always returns IReadOnlyList<object>), you can define a data object and project directly into it. This gives you strong typing and IntelliSense on the result.

Assume a OrderSummaryByCustomer data object with CustomerId (int) and OrderCount (int) properties:

IReadOnlyList<object> raw = await _orderViewRepository.GetTransformedListAsync(
    q => q.GroupBy(o => o.CustomerId)
          .Select(g => new OrderSummaryByCustomer
          {
              CustomerId = g.Key,
              OrderCount = g.Count(),
          }));

IReadOnlyList<OrderSummaryByCustomer> summaries = raw.Cast<OrderSummaryByCustomer>().ToList().AsReadOnly();

See also