Skip to content

Introduction

ExpressiveSharp is a Roslyn source generator that enables modern C# syntax in LINQ expression trees. It generates Expression<TDelegate> factory code at compile time, so you can use null-conditional operators, switch expressions, pattern matching, and more in queries against any LINQ provider.

Works With

ExpressiveSharp is provider-agnostic. It layers on top of IQueryable<T> and integrates with:

  • EF Core — every provider (SQLite, SQL Server, PostgreSQL, MySQL, Cosmos DB, Oracle, …) via ExpressiveSharp.EntityFrameworkCore
  • MongoDB — via ExpressiveSharp.MongoDB, translating to MQL aggregation pipelines
  • Any IQueryable<T> — wrap with .AsExpressive() and you get modern syntax on your own provider or any third-party one

The samples throughout these docs show the same query rendered against SQLite, PostgreSQL, SQL Server, and MongoDB side by side — so you always see how the construct translates for your target.

The Two Problems

When using C# with LINQ providers, you hit two walls:

1. Expression tree syntax restrictions

You write a perfectly reasonable query and hit:

error CS8072: An expression tree lambda may not contain a null propagating operator

Expression trees (Expression<Func<...>>) only support a restricted subset of C#. There is no ?., no switch expressions, no pattern matching. You end up writing ugly ternary chains instead of the clean code you would write anywhere else. For the full story of why this restriction has persisted since 2007, see The Expression Tree Problem.

2. Computed properties are opaque to LINQ providers

You define public string FullName => $"{FirstName} {LastName}" and use it in a query, but the provider cannot see inside the property getter. It either throws a runtime translation error, or worse, silently fetches the entire entity to evaluate FullName on the client (overfetching). The only workaround is to duplicate the logic as an inline expression in every query that needs it.

How ExpressiveSharp Works

ExpressiveSharp fixes both problems with a two-phase design:

Compile time (source generation)

Two Roslyn incremental source generators analyze your code:

  1. ExpressiveGenerator finds members decorated with [Expressive], validates them, and emits Expression.* factory code that builds the equivalent expression tree. These are registered in a per-assembly expression registry.

  2. PolyfillInterceptorGenerator uses C# 13 method interceptors to rewrite ExpressionPolyfill.Create() calls and IExpressiveQueryable<T> LINQ method calls, converting delegate lambdas into expression trees at their call sites.

Runtime (expression expansion)

When your query executes, an ExpressionVisitor walks the tree and replaces opaque [Expressive] member accesses with the pre-built expression trees. Provider-agnostic transformers then normalize the tree (stripping null-conditional patterns, flattening blocks, etc.) before handing off to the underlying LINQ provider.

Your LINQ query
    -> [Expressive member accesses replaced with generated expressions]
    -> [Transformers normalize the tree]
    -> Expanded expression tree
    -> Provider translation (SQL / MQL / …)
    -> Executed query

All expression trees are generated at compile time. There is no runtime reflection or expression compilation.

Which API Should I Use?

Mark computed properties and methods with [Expressive] to generate companion expression trees. Then choose how to wire them into your queries:

ScenarioAPI
Any IQueryable — modern syntax + [Expressive] expansion.AsExpressive()
EF Core — full integration (DbSet + async + Include)ExpressiveDbSet<T> / UseExpressives()
MongoDB.AsExpressive() on IMongoCollection<T>MongoDB integration
SQL window functions (ROW_NUMBER, RANK, etc.)WindowFunction.* (install RelationalExtensions package)
Advanced — build an Expression<T> inline, no attribute neededExpressionPolyfill.Create
Advanced — expand [Expressive] members in an existing expression tree.ExpandExpressives()
Advanced — make third-party/BCL members expressable[ExpressiveFor]

Comparison with Similar Libraries

FeatureExpressiveSharpProjectablesExpressionifyLinqKit
Source generator basedYesYesYesNo
Works with entity methodsYesYesYesPartial
Works with extension methodsYesYesYesYes
Composable membersYesYesNoPartial
Constructor projectionsYesYesNoNo
Null-conditional ?. rewritingYesYesNoNo
Switch expressions / pattern matchingYesYesNoNo
Block-bodied membersYes (experimental)Yes (experimental)NoNo
Enum method expansionYesYesNoNo
Inline expression creationYes (ExpressionPolyfill.Create)NoNoNo
Modern syntax in LINQ chainsYes (IExpressiveQueryable<T>)NoNoNo
SQL window functionsYes (RelationalExtensions)NoNoNo
String interpolation supportYesNoNoNo
Tuple literals supportYesNoNoNo
List patterns / index-rangeYesNoNoNo
with expressions (records)YesNoNoNo
Collection expressionsYesNoNoNo
Customizable transformer pipelineYesNoNoNo
MongoDB supportYesNoNoNo
Plugin architecture (EF Core)YesNoNoNo
External member mappingYes ([ExpressiveFor])NoNoNo
Not coupled to EF CoreYesNoNoNo
Roslyn analyzers and code fixesYesYesNoNo

Requirements

.NET 8.0.NET 9.0.NET 10.0
ExpressiveSharpC# 12C# 13C# 14
ExpressiveSharp.AbstractionsC# 12C# 13C# 14
ExpressiveSharp.EntityFrameworkCoreEF Core 8.xEF Core 9.xEF Core 10.x
ExpressiveSharp.MongoDBMongoDB.Driver 3.xMongoDB.Driver 3.xMongoDB.Driver 3.x
ExpressiveSharp.EntityFrameworkCore.RelationalExtensionsEF Core 8.xEF Core 9.xEF Core 10.x

Next Steps

Released under the MIT License.