Skip to content

Latest commit

 

History

History
185 lines (135 loc) · 5.61 KB

projectable-attribute.md

File metadata and controls

185 lines (135 loc) · 5.61 KB

[Projectable] Attribute

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.

Namespace

using EntityFrameworkCore.Projectables;

Target

Target Supported
Properties ✅
Methods ✅
Extension methods ✅
Constructors ✅ (v6.x+)
Indexers ❌

The attribute can be inherited by derived types (Inherited = true).

Properties

NullConditionalRewriteSupport

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.


UseMemberBody

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.


ExpandEnumMethods

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.


AllowBlockBody

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.


PolymorphicDispatch

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.


Complete Example

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
}