Skip to content

Limitations

This page documents the current limitations of ExpressiveSharp and guidance on how to work around them.

Members Must Have a Body

An [Expressive] member must have an expression body or a block body (with AllowBlockBody = true). Abstract members, interface declarations, and auto-properties without accessors produce diagnostic EXP0001.

csharp
// EXP0001 -- no body
[Expressive]
public string FullName { get; set; }

// Expression-bodied property -- works
[Expressive]
public string FullName => FirstName + " " + LastName;

Block Body Restrictions

When using block-bodied members, the following constructs are not supported:

ConstructDiagnosticSeverityReason
while / do-while loopsEXP0006WarningNo reliable expression tree equivalent
try / catch / finallyEXP0006WarningNo expression tree equivalent
throw statementsEXP0006WarningNot reliably translatable by LINQ providers
async / awaitEXP0005ErrorSide effects incompatible with expression trees
Assignments (x = y)EXP0005ErrorSide effects in expression trees
++ / --EXP0005ErrorSide effects in expression trees
csharp
// Not supported -- while loop
[Expressive(AllowBlockBody = true)]
public int Process()
{
    int total = 0;
    while (total < 100) { total += Price; }  // EXP0005
    return total;
}

// Use LINQ instead
[Expressive]
public double TotalPrice => LineItems.Sum(i => i.Price);

TIP

foreach loops are supported. They are emitted as Expression.Loop and rewritten to LINQ calls by the ConvertLoopsToLinq transformer. for loops are also emitted but produce an EXP0006 warning recommending foreach instead. See Block-Bodied Members for details.

Local Variable Inlining and Duplication

The FlattenBlockExpressions transformer (applied by UseExpressives() in EF Core) inlines local variables at every usage point. If a variable is referenced multiple times, the initializer expression is duplicated:

csharp
[Expressive(AllowBlockBody = true)]
public double Compute()
{
    var x = Price * Quantity;
    return x + x;
    // After FlattenBlockExpressions: (Price * Quantity) + (Price * Quantity)
}

This can increase SQL complexity and, in theory, change semantics if the initializer has observable side effects. The generator detects potential side effects and reports EXP0005.

Expression Tree Standard Restrictions

Since [Expressive] members are ultimately compiled to expression trees, all standard System.Linq.Expressions limitations apply:

RestrictionExplanation
No dynamic typingExpression trees must be statically typed
No ref / out parametersExpression.Parameter does not support by-ref semantics
No unsafe codePointers and address-of have no expression tree equivalent
No stackallocStack allocation cannot appear in expression trees
No multi-statement lambdas (in expression position)Expression-bodied members must be a single expression; block bodies go through the converter with the restrictions listed above

EF Core Translatable Operations Only

When targeting EF Core, the body of an [Expressive] member can only use operations that EF Core knows how to translate to SQL:

  • Mapped entity properties and navigation properties
  • Other [Expressive] members (transitively expanded)
  • EF Core built-in functions (EF.Functions.Like(...), DateTime.Now, etc.)
  • LINQ methods EF Core supports (Where, Sum, Any, Select, etc.)
  • String methods (Contains, StartsWith, ToUpper, etc.)
  • Math methods (Math.Abs, Math.Round, etc.)
csharp
// Runtime translation error -- Path.Combine has no SQL equivalent
[Expressive]
public string FilePath => Path.Combine(Directory, FileName);

// Works -- string concatenation is translated by EF Core
[Expressive]
public string FilePath => Directory + "/" + FileName;

Using ExpressiveFor for Unsupported Methods

If you need to use a method that EF Core cannot translate, provide a translatable equivalent via [ExpressiveFor]:

csharp
[ExpressiveFor(typeof(string), nameof(string.IsNullOrWhiteSpace))]
static bool IsNullOrWhiteSpace(string? s)
    => s == null || s.Trim().Length == 0;

Performance: First-Execution Overhead

ExpandExpressives() walks the expression tree and substitutes [Expressive] member references on every query execution. This adds a small cost to the first execution of each unique query shape. EF Core caches the compiled query afterward, so subsequent executions of the same shape skip the expansion entirely.

For standalone use (without EF Core), the resolved expressions are cached in ExpressiveResolver after the first lookup. The reflection-based slow path (for open-generic types) is also cached.

TIP

If first-execution latency is critical, warm up the cache by calling ExpandExpressives() on your query expressions during application startup.

Supported C# Features

Expression-Level

FeatureStatusNotes
Null-conditional ?. (member access and indexer)SupportedGenerates faithful null-check ternary; UseExpressives() strips it for SQL
Switch expressionsSupportedTranslated to nested CASE/ternary
Pattern matching (constant, type, relational, logical, property, positional)Supported
Declaration patterns with named variablesPartialWorks in switch arms only
String interpolationSupportedConverted to string.Concat calls
Tuple literalsSupported
Enum method expansionSupportedExpands enum extension methods into per-value ternary chains
C# 14 extension membersSupported
List patterns (fixed-length and slice)Supported
Index/range (^1, 1..3)Supported
with expressions (records)Supported
Collection expressions ([1, 2, 3], [..items])Supported
Dictionary indexer initializersSupported
this/base referencesSupported
Checked arithmetic (checked(...))Supported

Block-Body

FeatureStatus
return, if/else, switch statementsSupported
Local variable declarations (inlined)Supported
foreach loops (converted to LINQ)Supported
for loops (array/list iteration)Supported
while/do-while, try/catch, async/awaitNot supported
Assignments, ++, --Not supported

Window Functions: Experimental Status

The ExpressiveSharp.EntityFrameworkCore.RelationalExtensions package providing window functions (ROW_NUMBER, RANK, DENSE_RANK, NTILE) is experimental.

WARNING

EF Core has an open issue for native window function support. This package may be superseded when that ships. The API surface may change in future releases.

Window functions are limited to relational providers compatible with SQL:2003 window function syntax:

ProviderStatus
SQL ServerSupported
PostgreSQLSupported
SQLiteSupported
MySQLSupported
OracleSupported

Non-relational providers (Cosmos DB, in-memory) are not supported for window functions.

Released under the MIT License.