Skip to content

Computed Entity Properties

This recipe shows how to define reusable computed properties on your entities and use them across multiple query operations — all translated to SQL without any duplication.

The Pattern

Define computed values as [Projectable] properties directly on your entity. These properties can then be used in Select, Where, GroupBy, OrderBy, and any combination thereof.

Example: Order Totals

csharp
public class Order
{
    public int Id { get; set; }
    public decimal TaxRate { get; set; }
    public DateTime CreatedDate { get; set; }
    public ICollection<OrderItem> Items { get; set; }

    // Building blocks
    [Projectable]
    public decimal Subtotal => Items.Sum(item => item.Product.ListPrice * item.Quantity);

    [Projectable]
    public decimal Tax => Subtotal * TaxRate;

    // Composed from other projectables
    [Projectable]
    public decimal GrandTotal => Subtotal + Tax;
}

Use in Select

csharp
var summaries = dbContext.Orders
    .Select(o => new OrderSummaryDto
    {
        Id = o.Id,
        Subtotal = o.Subtotal,   // ✅ Inlined into SQL
        Tax = o.Tax,             // ✅ Inlined into SQL
        GrandTotal = o.GrandTotal // ✅ Inlined into SQL
    })
    .ToList();

Use in Where

csharp
// Only load high-value orders
var highValue = dbContext.Orders
    .Where(o => o.GrandTotal > 1000)
    .ToList();

Use in OrderBy

csharp
// Sort by computed value
var ranked = dbContext.Orders
    .OrderByDescending(o => o.GrandTotal)
    .Take(10)
    .ToList();

All Together

csharp
var report = dbContext.Orders
    .Where(o => o.GrandTotal > 500)
    .OrderByDescending(o => o.GrandTotal)
    .GroupBy(o => o.CreatedDate.Year)
    .Select(g => new
    {
        Year = g.Key,
        Count = g.Count(),
        TotalRevenue = g.Sum(o => o.GrandTotal)
    })
    .ToList();

All computed values are evaluated in the database — no data is fetched to memory for filtering or aggregation.

Example: User Profile

csharp
public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
    public DateTime? LastLoginDate { get; set; }

    [Projectable]
    public string FullName => FirstName + " " + LastName;

    [Projectable]
    public int Age => DateTime.Today.Year - BirthDate.Year
        - (DateTime.Today.DayOfYear < BirthDate.DayOfYear ? 1 : 0);

    [Projectable]
    public bool IsActive => LastLoginDate != null
        && LastLoginDate >= DateTime.UtcNow.AddDays(-30);
}
csharp
// Find active adult users, sorted by name
var results = dbContext.Users
    .Where(u => u.IsActive && u.Age >= 18)
    .OrderBy(u => u.FullName)
    .Select(u => new { u.FullName, u.Age })
    .ToList();

Example: Product Catalog

csharp
public class Product
{
    public decimal ListPrice { get; set; }
    public decimal DiscountRate { get; set; }
    public int StockQuantity { get; set; }
    public int ReorderPoint { get; set; }

    [Projectable]
    public decimal SalePrice => ListPrice * (1 - DiscountRate);

    [Projectable]
    public decimal SavingsAmount => ListPrice - SalePrice;

    [Projectable]
    public bool NeedsReorder => StockQuantity <= ReorderPoint;
}
csharp
// Products on sale that need restocking
var reorder = dbContext.Products
    .Where(p => p.NeedsReorder && p.SalePrice < 50)
    .OrderBy(p => p.StockQuantity)
    .Select(p => new
    {
        p.Id,
        p.SalePrice,
        p.SavingsAmount,
        p.StockQuantity
    })
    .ToList();

Tips

  • Compose freely — projectables can call other projectables. Build from simple to complex.
  • Use Limited mode in production for repeated queries — computed properties are cached after the first execution.
  • Keep it pure — projectable properties should be pure computations (no side effects). Everything must be translatable to SQL.
  • Avoid N+1 — if a projectable property references navigation properties, make sure to structure your queries so EF Core can generate a single efficient query.

Released under the MIT License.