🔍 Anonymous Types in EF Core — the secret weapon for clean, fast queries
If you’re still doing Select(entity => entity) and then mapping everything in memory… you’re probably over-fetching.
Anonymous types let you shape the result inside SQL, bringing back only what you need — with zero extra DTO classes.
✅ 1) Project only what you need (faster + less memory)
var users = await db.Users .Where(u => u.IsActive) .Select(u => new { u.Id, u.Name, u.Email }) .ToListAsync();
-- Generated query execution expression: SELECT u.Id, u.Name, u.Email FROM Users AS u WHERE u.IsActive ORDER BY u.Id ASC
This becomes a SQL query selecting only those columns.
✅ 2) Anonymous type as a “mini view model” for screens
var grid = await db.Orders .Where(o => o.Status == OrderStatus.Open) .Select(o => new { o.Id, Customer = o.Customer.Name, o.Total, o.CreatedAt }) .OrderByDescending(x => x.CreatedAt) .ToListAsync();
-- Generated query execution expression: SELECT o.Id, c.Name AS Customer, o.Total, o.CreatedAt FROM Orders AS o INNER JOIN Customers AS c ON o.CustomerId == c.Id WHERE o.Status == 'Open' ORDER BY o.CreatedAt DESC
✅ 3) Grouping with a composite key (anonymous type = key)
var summary = await db.Sales .GroupBy(s => new { s.StoreId, s.ProductId }) .Select(g => new { g.Key.StoreId, g.Key.ProductId, Qty = g.Sum(x => x.Quantity), Revenue = g.Sum(x => x.Quantity * x.UnitPrice) }) .ToListAsync();
-- Generated query execution expression: SELECT s.StoreId, s.ProductId, SUM(s.Quantity) AS Qty, SUM(s.Quantity * s.UnitPrice) AS Revenue FROM Sales AS s GROUP BY s.StoreId, s.ProductId ORDER BY s.StoreId ASC, s.ProductId ASC
Anonymous types shine here because they implement value-based equality automatically.
✅ 4) Join without pain (especially when you need multiple fields)
var data = await db.Users .Join(db.Subscriptions, u => u.Id, s => s.UserId, (u, s) => new { u.Name, s.Plan, s.ExpiresAt }) .ToListAsync();
-- Generated query execution expression: SELECT u.Name, s.Plan, s.ExpiresAt FROM Users AS u INNER JOIN Subscriptions AS s ON u.Id == s.UserId ORDER BY s.ExpiresAt ASC
⚠️ Gotchas (things that bite)
- Don’t return anonymous types from public methods (they’re internal to the method scope).
- Avoid non-translatable expressions inside projections (EF can only translate certain .NET operations to SQL).
- If you need reuse, promote the shape to a DTO/record — but start with anonymous types for speed.
Rule of thumb
✅ Anonymous types = perfect for queries + shaping data
✅ DTOs/records = best for boundaries (APIs, services, shared contracts)