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 operatorExpression 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:
ExpressiveGenerator finds members decorated with
[Expressive], validates them, and emitsExpression.*factory code that builds the equivalent expression tree. These are registered in a per-assembly expression registry.PolyfillInterceptorGenerator uses C# 13 method interceptors to rewrite
ExpressionPolyfill.Create()calls andIExpressiveQueryable<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 queryAll 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:
| Scenario | API |
|---|---|
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 needed | ExpressionPolyfill.Create |
Advanced — expand [Expressive] members in an existing expression tree | .ExpandExpressives() |
| Advanced — make third-party/BCL members expressable | [ExpressiveFor] |
Comparison with Similar Libraries
| Feature | ExpressiveSharp | Projectables | Expressionify | LinqKit |
|---|---|---|---|---|
| Source generator based | Yes | Yes | Yes | No |
| Works with entity methods | Yes | Yes | Yes | Partial |
| Works with extension methods | Yes | Yes | Yes | Yes |
| Composable members | Yes | Yes | No | Partial |
| Constructor projections | Yes | Yes | No | No |
Null-conditional ?. rewriting | Yes | Yes | No | No |
| Switch expressions / pattern matching | Yes | Yes | No | No |
| Block-bodied members | Yes (experimental) | Yes (experimental) | No | No |
| Enum method expansion | Yes | Yes | No | No |
| Inline expression creation | Yes (ExpressionPolyfill.Create) | No | No | No |
| Modern syntax in LINQ chains | Yes (IExpressiveQueryable<T>) | No | No | No |
| SQL window functions | Yes (RelationalExtensions) | No | No | No |
| String interpolation support | Yes | No | No | No |
| Tuple literals support | Yes | No | No | No |
| List patterns / index-range | Yes | No | No | No |
with expressions (records) | Yes | No | No | No |
| Collection expressions | Yes | No | No | No |
| Customizable transformer pipeline | Yes | No | No | No |
| MongoDB support | Yes | No | No | No |
| Plugin architecture (EF Core) | Yes | No | No | No |
| External member mapping | Yes ([ExpressiveFor]) | No | No | No |
| Not coupled to EF Core | Yes | No | No | No |
| Roslyn analyzers and code fixes | Yes | Yes | No | No |
Requirements
| .NET 8.0 | .NET 9.0 | .NET 10.0 | |
|---|---|---|---|
| ExpressiveSharp | C# 12 | C# 13 | C# 14 |
| ExpressiveSharp.Abstractions | C# 12 | C# 13 | C# 14 |
| ExpressiveSharp.EntityFrameworkCore | EF Core 8.x | EF Core 9.x | EF Core 10.x |
| ExpressiveSharp.MongoDB | MongoDB.Driver 3.x | MongoDB.Driver 3.x | MongoDB.Driver 3.x |
| ExpressiveSharp.EntityFrameworkCore.RelationalExtensions | EF Core 8.x | EF Core 9.x | EF Core 10.x |
Next Steps
- Quick Start — install, configure, and run your first query
- IExpressiveQueryable<T> — the core provider-agnostic API
- [Expressive] Properties — computed properties translated to your provider
