Skip to content

Commit e74afaf

Browse files
authored
Merge pull request #55 from EFNext/fix/docs
Various documentation tweaks
2 parents a7cb276 + 45c1de8 commit e74afaf

25 files changed

Lines changed: 127 additions & 100 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ error CS8072: An expression tree lambda may not contain a null propagating opera
2525

2626
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))
2727

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.
2929

3030
ExpressiveSharp fixes this. Write natural C# and the source generator builds the expression tree at compile time:
3131

docs/advanced/custom-transformers.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Custom Transformers
22

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.
44

55
## The `IExpressionTreeTransformer` Interface
66

@@ -122,9 +122,11 @@ When `UseExpressives()` is configured for EF Core, the transformer pipeline runs
122122

123123
1. **Per-member transformers** (from `[Expressive(Transformers = ...)]`) -- applied during expression resolution, before the tree is substituted into the query.
124124
2. **Built-in EF Core transformers** -- applied as part of `ExpandExpressives()`:
125+
- `ReplaceThrowWithDefault` (skip with `o => o.PreserveThrowExpressions()`)
125126
- `ConvertLoopsToLinq`
126127
- `RemoveNullConditionalPatterns`
127128
- `FlattenTupleComparisons`
129+
- `FlattenConcatArrayCalls`
128130
- `FlattenBlockExpressions`
129131
3. **Plugin-contributed transformers** (from `IExpressivePlugin.GetTransformers()`) -- applied after the built-in transformers.
130132

@@ -166,12 +168,14 @@ The `RelationalExtensions` package uses this exact pattern to register the `Rewr
166168

167169
| Transformer | Purpose | When to use manually |
168170
|---|---|---|
171+
| `ReplaceThrowWithDefault` | Replaces `throw` expressions with `default(T)` of the same type | Providers that cannot translate `Throw` (skipped via `PreserveThrowExpressions()`) |
169172
| `RemoveNullConditionalPatterns` | Strips `x != null ? x.Prop : default` ternaries | Databases that handle null propagation natively |
170173
| `FlattenBlockExpressions` | Inlines block-local variables, removes `Expression.Block` | LINQ providers that do not support block expressions |
171174
| `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 |
172176
| `ConvertLoopsToLinq` | Rewrites `LoopExpression` to LINQ method calls | Providers that cannot translate loop expressions |
173177

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.
175179

176180
## Testing Transformers
177181

docs/advanced/how-it-works.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,11 @@ Understanding the internals of ExpressiveSharp helps you use it effectively and
5454
| v |
5555
| +-----------------------------------------------------+ |
5656
| | Transformer Pipeline | |
57+
| | - ReplaceThrowWithDefault | |
5758
| | - ConvertLoopsToLinq | |
5859
| | - RemoveNullConditionalPatterns | |
5960
| | - FlattenTupleComparisons | |
61+
| | - FlattenConcatArrayCalls | |
6062
| | - FlattenBlockExpressions | |
6163
| | - (Plugin-contributed transformers) | |
6264
| +-----------------------------------------------------+ |
@@ -164,13 +166,15 @@ This means `[Expressive]` members can reference other `[Expressive]` members fre
164166

165167
### Transformer Pipeline
166168

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:
168170

169171
| Transformer | Purpose |
170172
|---|---|
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()`) |
171174
| `ConvertLoopsToLinq` | Rewrites `LoopExpression` (from `foreach`/`for`) into LINQ method calls (`Sum`, `Count`, `Any`, `All`) |
172175
| `RemoveNullConditionalPatterns` | Strips null-check ternaries (`x != null ? x.Prop : default` becomes `x.Prop`) for SQL null propagation |
173176
| `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 |
174178
| `FlattenBlockExpressions` | Inlines block-local variables and removes `Expression.Block` nodes |
175179

176180
Plugins can contribute additional transformers via `IExpressivePlugin.GetTransformers()`.

docs/advanced/limitations.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public string FullName { get; set; }
1717

1818
// Expression-bodied property -- works
1919
[Expressive]
20-
public string FullName => FirstName + " " + LastName;
20+
public string FullName => $"{FirstName} {LastName}";
2121
```
2222

2323
## Block Body Restrictions
@@ -96,9 +96,9 @@ When targeting EF Core, the body of an `[Expressive]` member can only use operat
9696
[Expressive]
9797
public string FilePath => Path.Combine(Directory, FileName);
9898

99-
// Works -- string concatenation is translated by EF Core
99+
// Works -- string interpolation is translated by EF Core
100100
[Expressive]
101-
public string FilePath => Directory + "/" + FileName;
101+
public string FilePath => $"{Directory}/{FileName}";
102102
```
103103

104104
::: info Using ExpressiveFor for Unsupported Methods

docs/guide/expressive-constructors.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ db.Orders
1111
.Select(o => new OrderSummaryDto
1212
{
1313
Id = o.Id,
14-
Description = "Order #" + o.Id,
14+
Description = $"Order #{o.Id}",
1515
Total = o.Items.Sum(i => i.UnitPrice * i.Quantity),
1616
})
1717
---setup---
@@ -26,7 +26,7 @@ public class OrderSummaryDto
2626
With an `[Expressive]` constructor, you define the projection once and use it everywhere:
2727

2828
::: expressive-sample
29-
db.Orders.Select(o => new OrderSummaryDto(o.Id, "Order #" + o.Id, o.Total()))
29+
db.Orders.Select(o => new OrderSummaryDto(o.Id, $"Order #{o.Id}", o.Total()))
3030
---setup---
3131
public class OrderSummaryDto
3232
{

docs/guide/expressive-queryable.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,11 @@ Most common `Queryable` methods are supported:
5959

6060
**Comparer overloads** (`IEqualityComparer<T>`, `IComparer<T>`) are also supported.
6161

62-
### .NET 10+ Additional Methods
62+
### .NET 9 / .NET 10 Additional Methods
6363

64-
On .NET 10 and later, these additional methods are available:
64+
On .NET 9 and later: `CountBy`, `AggregateBy`, `Index`.
6565

66-
- `LeftJoin`
67-
- `RightJoin`
68-
- `CountBy`
69-
- `AggregateBy`
70-
- `Index`
66+
On .NET 10 and later (in addition to the above): `LeftJoin`, `RightJoin`.
7167

7268
## Pattern Matching and Switch Expressions
7369

docs/guide/integrations/ef-core.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ services.AddDbContext<MyDbContext>(options =>
3636
1. **Expands `[Expressive]` member references** — walks query expression trees and replaces opaque property/method accesses with the generated expression trees
3737
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)
3838
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()`)
3940
- `ConvertLoopsToLinq` — converts loop expressions to LINQ method calls
4041
- `RemoveNullConditionalPatterns` — strips null-check ternaries for SQL providers
4142
- `FlattenTupleComparisons` — rewrites tuple field access to direct comparisons
@@ -207,7 +208,7 @@ The built-in `RelationalExtensions` package (for window functions) uses this plu
207208
|---------|-------------|
208209
| [`ExpressiveSharp`](https://www.nuget.org/packages/ExpressiveSharp/) | Core runtime — `[Expressive]` attribute, source generator, expression expansion, transformers |
209210
| [`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) |
211212

212213
::: info
213214
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.

docs/guide/integrations/mongodb.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ No custom MQL is emitted — MongoDB's own translator does all the heavy lifting
4545

4646
## `[Expressive]` Properties Are Unmapped from BSON
4747

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.
4949

5050
::: warning Ordering constraint
5151
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:

docs/guide/introduction.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Expression trees (`Expression<Func<...>>`) only support a restricted subset of C
2828

2929
### 2. Computed properties are opaque to LINQ providers
3030

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.
3232

3333
## How ExpressiveSharp Works
3434

@@ -101,13 +101,13 @@ Mark computed properties and methods with `[Expressive]` to generate companion e
101101

102102
## Requirements
103103

104-
| | .NET 8.0 | .NET 10.0 |
105-
|---|---|---|
106-
| **ExpressiveSharp** | C# 12 | C# 14 |
107-
| **ExpressiveSharp.Abstractions** | C# 12 | C# 14 |
108-
| **ExpressiveSharp.EntityFrameworkCore** | EF Core 8.x | EF Core 10.x |
109-
| **ExpressiveSharp.MongoDB** | MongoDB.Driver 3.x | MongoDB.Driver 3.x |
110-
| **ExpressiveSharp.EntityFrameworkCore.RelationalExtensions** | EF Core 8.x | EF Core 10.x |
104+
| | .NET 8.0 | .NET 9.0 | .NET 10.0 |
105+
|---|---|---|---|
106+
| **ExpressiveSharp** | C# 12 | C# 13 | C# 14 |
107+
| **ExpressiveSharp.Abstractions** | C# 12 | C# 13 | C# 14 |
108+
| **ExpressiveSharp.EntityFrameworkCore** | EF Core 8.x | EF Core 9.x | EF Core 10.x |
109+
| **ExpressiveSharp.MongoDB** | MongoDB.Driver 3.x | MongoDB.Driver 3.x | MongoDB.Driver 3.x |
110+
| **ExpressiveSharp.EntityFrameworkCore.RelationalExtensions** | EF Core 8.x | EF Core 9.x | EF Core 10.x |
111111

112112
## Next Steps
113113

docs/guide/migration-from-projectables.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ options.UseSqlServer(connectionString)
9090
.UseExpressives(opts => opts.AddPlugin(new MyPlugin()));
9191
```
9292

93-
`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()`.
9494

9595
### Null-Conditional Handling
9696

@@ -144,13 +144,13 @@ Pick based on whether you want the generator to declare the target property for
144144
// Before (Projectables)
145145
[Projectable(UseMemberBody = nameof(FullNameProjection))]
146146
public string FullName { get; init; }
147-
private string FullNameProjection => LastName + ", " + FirstName;
147+
private string FullNameProjection => $"{LastName}, {FirstName}";
148148

149149
// After (ExpressiveSharp) -- partial class, stub only; FullName is generated
150150
public partial class Customer
151151
{
152152
[ExpressiveProperty("FullName")]
153-
private string FullNameExpression => LastName + ", " + FirstName;
153+
private string FullNameExpression => $"{LastName}, {FirstName}";
154154
}
155155
```
156156

@@ -172,15 +172,15 @@ public string FullName => $"{FirstName} {LastName}".Trim().ToUpper();
172172

173173
[Projectable(UseMemberBody = nameof(FullNameProjection))]
174174
public string FullName => ...;
175-
private string FullNameProjection => FirstName + " " + LastName;
175+
private string FullNameProjection => $"{FirstName} {LastName}";
176176

177177
// After (ExpressiveSharp)
178178
using ExpressiveSharp.Mapping;
179179

180180
public string FullName => $"{FirstName} {LastName}".Trim().ToUpper();
181181

182182
[ExpressiveFor(nameof(FullName))]
183-
private string FullNameExpression => FirstName + " " + LastName;
183+
private string FullNameExpression => $"{FirstName} {LastName}";
184184
```
185185

186186
**Scenario 2: External/third-party type methods**
@@ -253,7 +253,7 @@ The `InterceptorsNamespaces` MSBuild property needed for method interceptors is
253253

254254
9. **Package consolidation** -- Remove all old packages and install `ExpressiveSharp.EntityFrameworkCore`.
255255

256-
10. **Target framework** -- ExpressiveSharp targets .NET 8.0 and .NET 10.0. If you are on .NET 6 or 7, you will need to upgrade.
256+
10. **Target framework** -- ExpressiveSharp targets .NET 8.0, .NET 9.0, and .NET 10.0. If you are on .NET 6 or 7, you will need to upgrade.
257257

258258
## Feature Comparison
259259

@@ -277,7 +277,7 @@ The `InterceptorsNamespaces` MSBuild property needed for method interceptors is
277277
| EF Core specific | Yes | No -- works standalone |
278278
| Compatibility modes | Full / Limited | Full only (simpler) |
279279
| Code generation approach | Syntax tree rewriting | Semantic (IOperation) analysis |
280-
| Target frameworks | .NET 6+ | .NET 8 / .NET 10 |
280+
| Target frameworks | .NET 6+ | .NET 8 / .NET 9 / .NET 10 |
281281

282282
## New Features Available After Migration
283283

0 commit comments

Comments
 (0)