You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -25,7 +25,7 @@ error CS8072: An expression tree lambda may not contain a null propagating opera
25
25
26
26
Expression trees (`Expression<Func<...>>`) only support a restricted subset of C# — no `?.`, no switch expressions, no pattern matching. So you end up writing ugly ternary chains instead of the clean code you'd write anywhere else. ([Why hasn't this been fixed?](https://efnext.github.io/ExpressiveSharp/guide/expression-tree-problem.html))
27
27
28
-
**2. Computed properties are opaque to LINQ providers.** You define `public string FullName => FirstName + " " + LastName` and use it in a query — but EF Core can't 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.
28
+
**2. Computed properties are opaque to LINQ providers.** You define `public string FullName => $"{FirstName} {LastName}"` and use it in a query — but EF Core can't 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.
29
29
30
30
ExpressiveSharp fixes this. Write natural C# and the source generator builds the expression tree at compile time:
Copy file name to clipboardExpand all lines: docs/advanced/custom-transformers.md
+6-2Lines changed: 6 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Custom Transformers
2
2
3
-
Expression transformers adapt expression trees for specific consumers at runtime. ExpressiveSharp includes four built-in transformers for EF Core compatibility, and you can create your own for custom LINQ providers or specialized rewriting needs.
3
+
Expression transformers adapt expression trees for specific consumers at runtime. ExpressiveSharp includes six built-in transformers for EF Core compatibility, and you can create your own for custom LINQ providers or specialized rewriting needs.
4
4
5
5
## The `IExpressionTreeTransformer` Interface
6
6
@@ -122,9 +122,11 @@ When `UseExpressives()` is configured for EF Core, the transformer pipeline runs
122
122
123
123
1.**Per-member transformers** (from `[Expressive(Transformers = ...)]`) -- applied during expression resolution, before the tree is substituted into the query.
124
124
2.**Built-in EF Core transformers** -- applied as part of `ExpandExpressives()`:
125
+
-`ReplaceThrowWithDefault` (skip with `o => o.PreserveThrowExpressions()`)
125
126
-`ConvertLoopsToLinq`
126
127
-`RemoveNullConditionalPatterns`
127
128
-`FlattenTupleComparisons`
129
+
-`FlattenConcatArrayCalls`
128
130
-`FlattenBlockExpressions`
129
131
3.**Plugin-contributed transformers** (from `IExpressivePlugin.GetTransformers()`) -- applied after the built-in transformers.
130
132
@@ -166,12 +168,14 @@ The `RelationalExtensions` package uses this exact pattern to register the `Rewr
166
168
167
169
| Transformer | Purpose | When to use manually |
168
170
|---|---|---|
171
+
|`ReplaceThrowWithDefault`| Replaces `throw` expressions with `default(T)` of the same type | Providers that cannot translate `Throw` (skipped via `PreserveThrowExpressions()`) |
|`FlattenBlockExpressions`| Inlines block-local variables, removes `Expression.Block`| LINQ providers that do not support block expressions |
171
174
|`FlattenTupleComparisons`| Replaces `ValueTuple` field access with underlying arguments | Providers that cannot translate `ValueTuple` construction |
175
+
|`FlattenConcatArrayCalls`| Rewrites `string.Concat(string[])` into 2/3/4-arg overloads | Providers that cannot translate `NewArrayInit` of non-constants |
172
176
|`ConvertLoopsToLinq`| Rewrites `LoopExpression` to LINQ method calls | Providers that cannot translate loop expressions |
173
177
174
-
All four are applied automatically by `UseExpressives()`. You only need to use them manually if you are working with a non-EF-Core LINQ provider or calling `ExpandExpressives()` directly.
178
+
All six are applied automatically by `UseExpressives()`. You only need to use them manually if you are working with a non-EF-Core LINQ provider or calling `ExpandExpressives()` directly.
@@ -164,13 +166,15 @@ This means `[Expressive]` members can reference other `[Expressive]` members fre
164
166
165
167
### Transformer Pipeline
166
168
167
-
After expansion, transformers adapt the expression tree for the target LINQ provider. When `UseExpressives()` is active, four built-in transformers run automatically:
169
+
After expansion, transformers adapt the expression tree for the target LINQ provider. When `UseExpressives()` is active, six built-in transformers run automatically:
168
170
169
171
| Transformer | Purpose |
170
172
|---|---|
173
+
|`ReplaceThrowWithDefault`| Replaces `throw` expressions with `default(T)` so providers that can't translate `Throw` still see a node of the same type (skip with `o => o.PreserveThrowExpressions()`) |
|`FlattenTupleComparisons`| Replaces `ValueTuple` field access with underlying arguments for direct comparison |
177
+
|`FlattenConcatArrayCalls`| Rewrites `string.Concat(string[])` (used by 5+-part interpolations) into 2/3/4-arg `string.Concat` calls EF Core can translate |
174
178
|`FlattenBlockExpressions`| Inlines block-local variables and removes `Expression.Block` nodes |
175
179
176
180
Plugins can contribute additional transformers via `IExpressivePlugin.GetTransformers()`.
1.**Expands `[Expressive]` member references** — walks query expression trees and replaces opaque property/method accesses with the generated expression trees
37
37
2.**Marks `[Expressive]` properties as unmapped** — adds a model convention that tells EF Core to ignore these properties in the database model (no corresponding column)
38
38
3.**Applies database-friendly transformers** (in this order):
39
+
-`ReplaceThrowWithDefault` — replaces `throw` expressions with `default(T)` so `Coalesce`/`Condition` shapes survive (skip with `o => o.PreserveThrowExpressions()`)
39
40
-`ConvertLoopsToLinq` — converts loop expressions to LINQ method calls
40
41
-`RemoveNullConditionalPatterns` — strips null-check ternaries for SQL providers
41
42
-`FlattenTupleComparisons` — rewrites tuple field access to direct comparisons
@@ -207,7 +208,7 @@ The built-in `RelationalExtensions` package (for window functions) uses this plu
|[`ExpressiveSharp.EntityFrameworkCore`](https://www.nuget.org/packages/ExpressiveSharp.EntityFrameworkCore/)| EF Core integration — `UseExpressives()`, `ExpressiveDbSet<T>`, Include/ThenInclude, async methods, analyzers and code fixes |
210
-
|[`ExpressiveSharp.EntityFrameworkCore.RelationalExtensions`](https://www.nuget.org/packages/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/)| Relational extensions — `ExecuteUpdate`/`ExecuteUpdateAsync` with modern syntax, SQL window functions (ROW_NUMBER, RANK, DENSE_RANK, NTILE) |
211
+
|[`ExpressiveSharp.EntityFrameworkCore.RelationalExtensions`](https://www.nuget.org/packages/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/)| Relational extensions — `ExecuteUpdate`/`ExecuteUpdateAsync` with modern syntax, SQL window functions (ranking, aggregate, navigation) |
211
212
212
213
::: info
213
214
The `ExpressiveSharp.EntityFrameworkCore` package bundles Roslyn analyzers and code fixes from `ExpressiveSharp.EntityFrameworkCore.CodeFixers`. These provide compile-time diagnostics and IDE quick-fix actions for common issues like missing `[Expressive]` attributes.
Copy file name to clipboardExpand all lines: docs/guide/integrations/mongodb.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -45,7 +45,7 @@ No custom MQL is emitted — MongoDB's own translator does all the heavy lifting
45
45
46
46
## `[Expressive]` Properties Are Unmapped from BSON
47
47
48
-
ExpressiveSharp provides a MongoDB `IClassMapConvention` that unmaps every `[Expressive]`-decorated property from the BSON class map, so the property's backing field is not persisted to documents. This matters most for [synthesized properties](../../reference/expressive-for#synthesizing-a-property-with-synthesize-true), which have a writable `init` accessor and would otherwise be serialized as a real BSON field.
48
+
ExpressiveSharp provides a MongoDB `IClassMapConvention` that unmaps every `[Expressive]`-decorated property from the BSON class map, so the property's backing field is not persisted to documents. This matters most for [synthesized properties](../../reference/expressive-property), which have a writable `init` accessor and would otherwise be serialized as a real BSON field.
49
49
50
50
::: warning Ordering constraint
51
51
MongoDB builds and caches a class map the first time you call `IMongoDatabase.GetCollection<T>()` for a given `T`. A convention registered *after* that call does not apply to the cached map. If any of your document types use `[Expressive]`, register the convention before the first `GetCollection<T>` call:
Copy file name to clipboardExpand all lines: docs/guide/introduction.md
+8-8Lines changed: 8 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -28,7 +28,7 @@ Expression trees (`Expression<Func<...>>`) only support a restricted subset of C
28
28
29
29
### 2. Computed properties are opaque to LINQ providers
30
30
31
-
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.
31
+
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.
32
32
33
33
## How ExpressiveSharp Works
34
34
@@ -101,13 +101,13 @@ Mark computed properties and methods with `[Expressive]` to generate companion e
`UseExpressives()` automatically registers four transformers as global defaults (`ConvertLoopsToLinq`, `RemoveNullConditionalPatterns`, `FlattenTupleComparisons`, `FlattenBlockExpressions`), sets up the query compiler decorator, and configures model conventions.
93
+
`UseExpressives()` automatically registers six transformers as global defaults (`ReplaceThrowWithDefault`, `ConvertLoopsToLinq`, `RemoveNullConditionalPatterns`, `FlattenTupleComparisons`, `FlattenConcatArrayCalls`, `FlattenBlockExpressions`), sets up the query compiler decorator, and configures model conventions. `ReplaceThrowWithDefault` can be opted out via `o => o.PreserveThrowExpressions()`.
94
94
95
95
### Null-Conditional Handling
96
96
@@ -144,13 +144,13 @@ Pick based on whether you want the generator to declare the target property for
0 commit comments