Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ CI targets both .NET 8.0 and .NET 10.0 SDKs.

1. **Compile-time (source generation):** Two incremental generators in `ExpressiveSharp.Generator` (targets netstandard2.0):
- `ExpressiveGenerator` — finds `[Expressive]` members, validates them via `ExpressiveInterpreter`, emits expression trees via `ExpressionTreeEmitter`, and builds a runtime registry via `ExpressionRegistryEmitter`
- `PolyfillInterceptorGenerator` — uses C# 13 `[InterceptsLocation]` to rewrite `ExpressionPolyfill.Create()` and `IRewritableQueryable<T>` LINQ call sites from delegate form to expression tree form. Supports all standard `Queryable` methods, multi-lambda methods (Join, GroupJoin, GroupBy overloads), non-lambda-first methods (Zip, ExceptBy, etc.), and custom target types via `[PolyfillTarget]` (e.g., EF Core's `EntityFrameworkQueryableExtensions` for async methods)
- `PolyfillInterceptorGenerator` — uses C# 13 `[InterceptsLocation]` to rewrite `ExpressionPolyfill.Create()` and `IExpressiveQueryable<T>` LINQ call sites from delegate form to expression tree form. Supports all standard `Queryable` methods, multi-lambda methods (Join, GroupJoin, GroupBy overloads), non-lambda-first methods (Zip, ExceptBy, etc.), and custom target types via `[PolyfillTarget]` (e.g., EF Core's `EntityFrameworkQueryableExtensions` for async methods)

2. **Runtime:** `ExpressiveResolver` looks up generated expressions by (DeclaringType, MemberName, ParameterTypes). `ExpressiveReplacer` is an `ExpressionVisitor` that substitutes `[Expressive]` member accesses with the generated expression trees. Transformers (in `Transformers/`) post-process trees for provider compatibility.

Expand All @@ -55,9 +55,9 @@ CI targets both .NET 8.0 and .NET 10.0 SDKs.
- `src/ExpressiveSharp.Generator/PolyfillInterceptorGenerator.cs` — interceptor generation. Dedicated emitters for complex methods (Join, GroupJoin, GroupBy multi-lambda), enhanced generic fallback (`EmitGenericSingleLambda`) for single-lambda methods with non-lambda arg forwarding, `[PolyfillTarget]` support for custom target types
- `src/ExpressiveSharp/Services/ExpressiveResolver.cs` — runtime expression registry lookup
- `src/ExpressiveSharp/PolyfillTargetAttribute.cs` — specifies a non-`Queryable` target type for interceptor forwarding (e.g., `EntityFrameworkQueryableExtensions`)
- `src/ExpressiveSharp/Extensions/RewritableQueryableLinqExtensions.cs` — delegate-based LINQ stubs on `IRewritableQueryable<T>` (~85 intercepted + ~15 passthrough methods)
- `src/ExpressiveSharp.EntityFrameworkCore/Extensions/RewritableQueryableEfCoreExtensions.cs` — EF Core-specific stubs: chain-continuity (AsNoTracking, TagWith, etc.), Include/ThenInclude, and async lambda methods (AnyAsync, SumAsync, etc.)
- `src/ExpressiveSharp.EntityFrameworkCore/IIncludableRewritableQueryable.cs` — hybrid interface bridging `IIncludableQueryable` and `IRewritableQueryable` for Include/ThenInclude chain continuity
- `src/ExpressiveSharp/Extensions/ExpressiveQueryableLinqExtensions.cs` — delegate-based LINQ stubs on `IExpressiveQueryable<T>` (~85 intercepted + ~15 passthrough methods)
- `src/ExpressiveSharp.EntityFrameworkCore/Extensions/ExpressiveQueryableEfCoreExtensions.cs` — EF Core-specific stubs: chain-continuity (AsNoTracking, TagWith, etc.), Include/ThenInclude, and async lambda methods (AnyAsync, SumAsync, etc.)
- `src/ExpressiveSharp.EntityFrameworkCore/IIncludableExpressiveQueryable.cs` — hybrid interface bridging `IIncludableQueryable` and `IExpressiveQueryable` for Include/ThenInclude chain continuity
- `src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowFunction.cs` — public marker methods for all SQL window functions (ranking, aggregate, navigation). All throw at runtime; translated to SQL by the method call translator
- `src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowDefinition.cs` — fluent builder types: `PartitionedWindowDefinition` → `OrderedWindowDefinition` → `FramedWindowDefinition`. Type-safe chain ensures ORDER BY before ranking, frame only on aggregates/value functions
- `src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowFrameBound.cs` — frame boundary markers: `UnboundedPreceding` (property), `Preceding(n)` (method), `CurrentRow` (property), `Following(n)` (method), `UnboundedFollowing` (property)
Expand All @@ -75,15 +75,22 @@ ExpressiveSharp.Abstractions (attributes + source generator, net8.0;net10.0)
ExpressiveSharp (core runtime, net8.0;net10.0)
└── ExpressiveSharp.Abstractions
Provides: ExpressiveResolver, ExpressiveReplacer, expression transformers,
IRewritableQueryable<T>, ExpressionPolyfill, .ExpandExpressives(), .AsExpressive()
IExpressiveQueryable<T>, ExpressionPolyfill, .ExpandExpressives(), .AsExpressive()

ExpressiveSharp.Generator (source generator, netstandard2.0)
└── Microsoft.CodeAnalysis.CSharp 5.0.0

ExpressiveSharp.MongoDB (net8.0;net10.0)
├── ExpressiveSharp
├── MongoDB.Driver 3.4.0
└── Provides: IRewritableMongoQueryable<T>, ExpressiveMongoCollection<T>,
ExpressiveMongoQueryProvider (decorating IQueryProvider/IMongoQueryProvider),
async lambda stubs with [PolyfillTarget(typeof(MongoQueryable))]

ExpressiveSharp.EntityFrameworkCore (net8.0;net10.0)
├── ExpressiveSharp
├── EF Core 8.0.25 / 10.0.0
└── Provides: ExpressiveDbSet<T>, IIncludableRewritableQueryable<T,P>,
└── Provides: ExpressiveDbSet<T>, IIncludableExpressiveQueryable<T,P>,
chain-continuity stubs, async lambda stubs with [PolyfillTarget]

ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions (net8.0;net10.0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public class BenchEntity

for (var i = 0; i < callSiteCount; i++)
{
sb.AppendLine($" public static IQueryable<int> Query{i}(IRewritableQueryable<BenchEntity> q)");
sb.AppendLine($" public static IQueryable<int> Query{i}(IExpressiveQueryable<BenchEntity> q)");
sb.AppendLine($" => q.Select(x => x.Id + {i});");
}

Expand Down
2 changes: 1 addition & 1 deletion docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default defineConfig({
{ text: 'Extension Members', link: '/guide/extension-members' },
{ text: 'Constructor Projections', link: '/guide/expressive-constructors' },
{ text: 'ExpressionPolyfill.Create', link: '/guide/expression-polyfill' },
{ text: 'IRewritableQueryable<T>', link: '/guide/rewritable-queryable' },
{ text: 'IExpressiveQueryable<T>', link: '/guide/expressive-queryable' },
{ text: 'EF Core Integration', link: '/guide/ef-core-integration' },
]
},
Expand Down
6 changes: 3 additions & 3 deletions docs/advanced/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Understanding the internals of ExpressiveSharp helps you use it effectively and
| +-----------------------------------------------------+ |
| | PolyfillInterceptorGenerator | |
| | - Intercepts ExpressionPolyfill.Create calls | |
| | - Intercepts IRewritableQueryable<T> LINQ methods | |
| | - Intercepts IExpressiveQueryable<T> LINQ methods | |
| | - Uses C# 13 [InterceptsLocation] attribute | |
| +-----------------------------------------------------+ |
| | |
Expand Down Expand Up @@ -93,7 +93,7 @@ Unlike syntax-tree-based approaches (such as EntityFrameworkCore.Projectables, w
This generator uses C# 13 method interceptors (`[InterceptsLocation]`) to replace call sites at compile time:

- **`ExpressionPolyfill.Create` calls** -- The delegate-form lambda is rewritten to an expression tree, enabling modern syntax without any attribute.
- **`IRewritableQueryable<T>` LINQ methods** -- Methods like `Where`, `Select`, `OrderBy`, etc. that accept delegates are rewritten to their `Queryable` equivalents that accept `Expression<Func<...>>`.
- **`IExpressiveQueryable<T>` LINQ methods** -- Methods like `Where`, `Select`, `OrderBy`, etc. that accept delegates are rewritten to their `Queryable` equivalents that accept `Expression<Func<...>>`.

The interceptor handles complex multi-lambda methods (Join, GroupJoin, GroupBy overloads), non-lambda-first methods (Zip, ExceptBy), and custom target types registered via `[PolyfillTarget]` (such as EF Core's `EntityFrameworkQueryableExtensions` for async methods).

Expand Down Expand Up @@ -228,7 +228,7 @@ The built-in `RelationalExtensions` package uses this architecture to register w
| Build | `ExpressiveInterpreter` | Validates members, extracts descriptors |
| Build | `ExpressionTreeEmitter` | Maps `IOperation` nodes to `Expression.*` factory calls |
| Build | `ExpressionRegistryEmitter` | Generates per-assembly expression registry |
| Build | `PolyfillInterceptorGenerator` | Intercepts `ExpressionPolyfill.Create` and `IRewritableQueryable<T>` call sites |
| Build | `PolyfillInterceptorGenerator` | Intercepts `ExpressionPolyfill.Create` and `IExpressiveQueryable<T>` call sites |
| Runtime | `ExpressiveResolver` | Locates generated expressions via registry or reflection |
| Runtime | `ExpressiveReplacer` | Walks and replaces `[Expressive]` member references |
| Runtime | `ExpressiveQueryCompiler` | EF Core decorator: expands before compilation |
Expand Down
8 changes: 4 additions & 4 deletions docs/guide/ef-core-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ String interpolation with format specifiers like `$"{Price:F2}"` generates `ToSt

## ExpressiveDbSet\<T\>

`ExpressiveDbSet<T>` is the primary API for using modern syntax directly on a `DbSet<T>`. It combines `[Expressive]` member expansion with `IRewritableQueryable<T>` modern syntax support:
`ExpressiveDbSet<T>` is the primary API for using modern syntax directly on a `DbSet<T>`. It combines `[Expressive]` member expansion with `IExpressiveQueryable<T>` modern syntax support:

```csharp
public class MyDbContext : DbContext
Expand Down Expand Up @@ -139,7 +139,7 @@ Supported async methods:

## Chain Continuity Stubs

The following EF Core operations preserve the `ExpressiveDbSet<T>`/`IRewritableQueryable<T>` chain:
The following EF Core operations preserve the `ExpressiveDbSet<T>`/`IExpressiveQueryable<T>` chain:

```csharp
var orders = ctx.Orders
Expand Down Expand Up @@ -182,7 +182,7 @@ await ctx.Orders
Switch expressions and null-conditional operators inside `SetProperty` value lambdas are normally rejected by the C# compiler in expression tree contexts. The source generator converts them to `CASE WHEN` and `COALESCE` SQL expressions.

::: info
`ExecuteDelete` works on `IRewritableQueryable<T>` / `ExpressiveDbSet<T>` without any additional setup — it has no lambda parameter, so no interception is needed.
`ExecuteDelete` works on `IExpressiveQueryable<T>` / `ExpressiveDbSet<T>` without any additional setup — it has no lambda parameter, so no interception is needed.
:::

::: warning
Expand Down Expand Up @@ -302,6 +302,6 @@ var results = await ctx.Orders
## Next Steps

- [Window Functions](./window-functions) -- SQL window functions via the RelationalExtensions package
- [IRewritableQueryable\<T\>](./rewritable-queryable) -- modern syntax on any `IQueryable`
- [IExpressiveQueryable\<T\>](./expressive-queryable) -- modern syntax on any `IQueryable`
- [[Expressive] Properties](./expressive-properties) -- computed properties in depth
- [Quick Start](./quickstart) -- minimal setup walkthrough
2 changes: 1 addition & 1 deletion docs/guide/expression-polyfill.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,5 @@ Use `[Expressive]` when the logic belongs on an entity and will be reused across
## Next Steps

- [[Expressive] Properties](./expressive-properties) -- reusable computed properties
- [IRewritableQueryable\<T\>](./rewritable-queryable) -- modern syntax directly in LINQ chains
- [IExpressiveQueryable\<T\>](./expressive-queryable) -- modern syntax directly in LINQ chains
- [EF Core Integration](./ef-core-integration) -- full EF Core setup
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# IRewritableQueryable\<T\>
# IExpressiveQueryable\<T\>

`IRewritableQueryable<T>` enables modern C# syntax directly in LINQ chains -- null-conditional operators, switch expressions, and pattern matching work in `.Where()`, `.Select()`, `.OrderBy()`, and more, on any `IQueryable<T>`.
`IExpressiveQueryable<T>` enables modern C# syntax directly in LINQ chains -- null-conditional operators, switch expressions, and pattern matching work in `.Where()`, `.Select()`, `.OrderBy()`, and more, on any `IQueryable<T>`.

## Basic Usage

Expand All @@ -21,7 +21,7 @@ The source generator intercepts these calls at compile time and rewrites the del

## How It Works

When you call `.AsExpressive()`, you get back an `IRewritableQueryable<T>` wrapper. This wrapper exposes the same LINQ methods as `IQueryable<T>`, but they accept `Func<...>` delegates instead of `Expression<Func<...>>`.
When you call `.AsExpressive()`, you get back an `IExpressiveQueryable<T>` wrapper. This wrapper exposes the same LINQ methods as `IQueryable<T>`, but they accept `Func<...>` delegates instead of `Expression<Func<...>>`.

At compile time, the `PolyfillInterceptorGenerator` uses C# 13 method interceptors to replace each call site with code that:

Expand Down Expand Up @@ -58,7 +58,7 @@ Most common `Queryable` methods are supported:
**Set operations:**
`ExceptBy`, `IntersectBy`, `UnionBy`, `DistinctBy`

**Chain-preserving operators** (return `IRewritableQueryable<T>`):
**Chain-preserving operators** (return `IExpressiveQueryable<T>`):
`Take`, `Skip`, `Distinct`, `Reverse`, `DefaultIfEmpty`, `Append`, `Prepend`, `Concat`, `Union`, `Intersect`, `Except`, `SkipWhile`, `TakeWhile`

**Comparer overloads** (`IEqualityComparer<T>`, `IComparer<T>`) are also supported.
Expand All @@ -75,7 +75,7 @@ On .NET 10 and later, these additional methods are available:

## EF Core: Include and ThenInclude

When using `IRewritableQueryable<T>` with EF Core, `Include` and `ThenInclude` are fully supported with chain continuity:
When using `IExpressiveQueryable<T>` with EF Core, `Include` and `ThenInclude` are fully supported with chain continuity:

```csharp
var orders = ctx.Set<Order>()
Expand All @@ -86,15 +86,15 @@ var orders = ctx.Set<Order>()
.ToList();
```

The `Include`/`ThenInclude` calls return `IIncludableRewritableQueryable<TEntity, TProperty>`, a hybrid interface that preserves both the includable chain and the rewritable chain.
The `Include`/`ThenInclude` calls return `IIncludableExpressiveQueryable<TEntity, TProperty>`, a hybrid interface that preserves both the includable chain and the rewritable chain.

::: info
`Include` and `ThenInclude` accept standard `Expression<Func<...>>` lambdas (not rewritten delegates), since navigation property paths do not typically need modern syntax. The chain continuity ensures you can seamlessly go from `Include`/`ThenInclude` back to rewritable LINQ methods like `Where` and `Select`.
:::

## EF Core: Async Lambda Methods

All EF Core async methods that accept a lambda predicate or selector are supported on `IRewritableQueryable<T>`:
All EF Core async methods that accept a lambda predicate or selector are supported on `IExpressiveQueryable<T>`:

**Async predicates:**
`AnyAsync`, `AllAsync`, `CountAsync`, `LongCountAsync`
Expand Down Expand Up @@ -123,7 +123,7 @@ These async methods are forwarded to `EntityFrameworkQueryableExtensions` at com

## EF Core: Chain Continuity Stubs

The following EF Core operations preserve the `IRewritableQueryable<T>` chain, so you can continue using modern syntax after calling them:
The following EF Core operations preserve the `IExpressiveQueryable<T>` chain, so you can continue using modern syntax after calling them:

- `AsNoTracking()`, `AsNoTrackingWithIdentityResolution()`, `AsTracking()`
- `IgnoreQueryFilters()`, `IgnoreAutoIncludes()`
Expand All @@ -146,7 +146,7 @@ var orders = ctx.Set<Order>()
Requires the `ExpressiveSharp.EntityFrameworkCore.RelationalExtensions` package and `.UseExpressives(o => o.UseRelationalExtensions())` configuration. Available on EF Core 8 and 9. On EF Core 10+, `ExecuteUpdate` natively accepts delegates — use `ExpressionPolyfill.Create()` for modern syntax in individual `SetProperty` value expressions.
:::

`ExecuteUpdate` and `ExecuteUpdateAsync` are supported on `IRewritableQueryable<T>`, enabling modern C# syntax inside `SetProperty` value expressions — which is normally impossible in expression trees:
`ExecuteUpdate` and `ExecuteUpdateAsync` are supported on `IExpressiveQueryable<T>`, enabling modern C# syntax inside `SetProperty` value expressions — which is normally impossible in expression trees:

```csharp
ctx.ExpressiveSet<Product>()
Expand All @@ -162,7 +162,7 @@ ctx.ExpressiveSet<Product>()

This generates a single SQL `UPDATE` with `CASE WHEN` and `COALESCE` expressions — no entity loading required.

`ExecuteDelete` works out of the box on `IRewritableQueryable<T>` without any stubs (it has no lambda parameter):
`ExecuteDelete` works out of the box on `IExpressiveQueryable<T>` without any stubs (it has no lambda parameter):

```csharp
ctx.ExpressiveSet<Product>()
Expand All @@ -172,7 +172,7 @@ ctx.ExpressiveSet<Product>()

## IAsyncEnumerable Support

`IRewritableQueryable<T>` supports `AsAsyncEnumerable()` for streaming results:
`IExpressiveQueryable<T>` supports `AsAsyncEnumerable()` for streaming results:

```csharp
await foreach (var order in ctx.Set<Order>()
Expand Down
6 changes: 3 additions & 3 deletions docs/guide/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ 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 `IRewritableQueryable<T>` LINQ method calls, converting delegate lambdas into expression trees at their call sites.
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)

Expand All @@ -54,7 +54,7 @@ Mark computed properties and methods with `[Expressive]` to generate companion e
| Scenario | API |
|---|---|
| **EF Core** -- modern syntax + `[Expressive]` expansion on `DbSet` | [`ExpressiveDbSet<T>`](./ef-core-integration) (or [`UseExpressives()`](./ef-core-integration) for global expansion) |
| **Any `IQueryable`** -- modern syntax + `[Expressive]` expansion | [`.AsExpressive()`](./rewritable-queryable) |
| **Any `IQueryable`** -- modern syntax + `[Expressive]` expansion | [`.AsExpressive()`](./expressive-queryable) |
| **EF Core** -- SQL window functions (ROW_NUMBER, RANK, etc.) | [`WindowFunction.*`](./window-functions) (install `RelationalExtensions` package) |
| **Advanced** -- build an `Expression<T>` inline, no attribute needed | [`ExpressionPolyfill.Create`](./expression-polyfill) |
| **Advanced** -- expand `[Expressive]` members in an existing expression tree | `.ExpandExpressives()` |
Expand All @@ -74,7 +74,7 @@ Mark computed properties and methods with `[Expressive]` to generate companion e
| 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 (`IRewritableQueryable<T>`) | 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 |
Expand Down
Loading
Loading