The ProjectableAttribute is the entry point for this library. Place it on any property or method to tell the source generator to produce a companion expression tree for it.
using EntityFrameworkCore.Projectables;| Target | Supported |
|---|---|
| Properties | ✅ |
| Methods | ✅ |
| Extension methods | ✅ |
| Constructors | ✅ (v6.x+) |
| Indexers | ❌ |
The attribute can be inherited by derived types (Inherited = true).
Type: NullConditionalRewriteSupport
Default: NullConditionalRewriteSupport.None
Controls how null-conditional operators (?.) in the member body are handled by the source generator.
[Projectable(NullConditionalRewriteSupport = NullConditionalRewriteSupport.Rewrite)]
public string? FullAddress => Location?.AddressLine1 + " " + Location?.City;| Value | Behavior |
|---|---|
None (default) |
Null-conditional operators are not allowed — the generator raises error EFP0002. |
Ignore |
Null-conditional operators are stripped — A?.B becomes A.B. Safe for databases where null propagates implicitly (SQL Server). |
Rewrite |
Null-conditional operators are rewritten as explicit null checks — A?.B becomes A != null ? A.B : null. Safer but may increase SQL complexity. |
See Null-Conditional Rewrite for full details.
Type: string?
Default: null
Tells the generator to use a different member's body as the source for the generated expression tree. Useful when the projectable member's body is not directly available (e.g., interface implementation, abstract member).
public class Order
{
// The actual computation is defined here
private decimal ComputeGrandTotal() => Subtotal + Tax;
// The projectable member delegates to it
[Projectable(UseMemberBody = nameof(ComputeGrandTotal))]
public decimal GrandTotal => ComputeGrandTotal();
}See Use Member Body for full details.
Type: bool
Default: false
When set to true, method calls on enum values inside this projectable member are expanded into a chain of ternary expressions — one branch per enum value. This allows enum helper methods (like display name lookups) to be translated to SQL CASE expressions.
[Projectable(ExpandEnumMethods = true)]
public string StatusName => Status.GetDisplayName();See Expand Enum Methods for full details.
Type: bool
Default: false
Enables block-bodied member support (experimental). Without this flag, using a block body with [Projectable] produces warning EFP0001. Setting this to true suppresses the warning.
[Projectable(AllowBlockBody = true)]
public string Category
{
get
{
if (Score > 100) return "High";
else if (Score > 50) return "Medium";
else return "Low";
}
}See Block-Bodied Members for full details.
Type: bool
Default: false
Enables polymorphic dispatch support for abstract/virtual/overwritten members.
public class Foo{
[Projectable(PolymorphicDispatch = true)]
public virtual string Name() => "Foo";
// Converted to: @this is Bar ? "Bar" : "Foo"
}
public class Bar : Foo{
[Projectable(PolymorphicDispatch = true)]
public override string Name() => "Bar";
// Converted to: "Bar" as it has no derived types
}See Polymorphic Dispatch for full details.
public class Order
{
public OrderStatus Status { get; set; }
public decimal TaxRate { get; set; }
public Address? ShippingAddress { get; set; }
public ICollection<OrderItem> Items { get; set; }
// Simple computed property
[Projectable]
public decimal Subtotal => Items.Sum(i => i.Price * i.Quantity);
// Composing projectables
[Projectable]
public decimal GrandTotal => Subtotal * (1 + TaxRate);
// Handling nullable navigation
[Projectable(NullConditionalRewriteSupport = NullConditionalRewriteSupport.Rewrite)]
public string? ShippingLine => ShippingAddress?.AddressLine1 + ", " + ShippingAddress?.City;
// Enum expansion
[Projectable(ExpandEnumMethods = true)]
public string StatusLabel => Status.GetDisplayName();
// Block-bodied (experimental)
[Projectable(AllowBlockBody = true)]
public string Priority
{
get
{
if (GrandTotal > 1000) return "High";
if (GrandTotal > 500) return "Medium";
return "Normal";
}
}
// Polymorphic Dispatch
[Projectable(PolymorphicDispatch = true)]
public virtual string Currency() => "EUR";
// Converted to: @this is Preorder ? "USD" : "EUR"
}
public class Preorder : Order{
[Projectable(PolymorphicDispatch = true)]
public override string Currency() => "USD";
// Converted to: "USD" as it has no derived types
}