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();