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
+54-1Lines changed: 54 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -90,6 +90,7 @@ Mark computed properties and methods with [`[Expressive]`](#expressive-attribute
90
90
|**Any `IQueryable`** — modern syntax + `[Expressive]` expansion |[`.WithExpressionRewrite()`](#irewritablequeryt)|
91
91
|**Advanced** — build an `Expression<T>` inline, no attribute needed |[`ExpressionPolyfill.Create`](#expressionpolyfillcreate)|
92
92
|**Advanced** — expand `[Expressive]` members in an existing expression tree |[`.ExpandExpressives()`](#expressive-attribute)|
93
+
|**Advanced** — make third-party/BCL members expressable |[`[ExpressiveFor]`](#expressivefor--external-member-mapping)|
93
94
94
95
## Usage
95
96
@@ -327,11 +328,63 @@ public double Total => Price * Quantity;
327
328
expr.ExpandExpressives(newMyTransformer());
328
329
```
329
330
331
+
## `[ExpressiveFor]` — External Member Mapping
332
+
333
+
Provide expression-tree bodies for members on types you don't own — BCL methods, third-party libraries, or your own members that can't use `[Expressive]` directly. This lets you use those members in EF Core queries that would otherwise fail with "could not be translated".
334
+
335
+
```csharp
336
+
usingExpressiveSharp.Mapping;
337
+
338
+
// Static method — params match the target signature
> **Note:** If a member already has `[Expressive]`, adding `[ExpressiveFor]` targeting it is a compile error (EXP0019). `[ExpressiveFor]` is for members that *don't* have `[Expressive]`.
382
+
330
383
## How It Works
331
384
332
385
ExpressiveSharp uses two Roslyn source generators:
333
386
334
-
1.**`ExpressiveGenerator`** — Finds `[Expressive]` members, analyzes them at the semantic level (IOperation), and generates `Expression<Func<...>>` factory code using `Expression.*` calls. Registers them in a per-assembly expression registry for runtime lookup.
387
+
1.**`ExpressiveGenerator`** — Finds `[Expressive]`and `[ExpressiveFor]`members, analyzes them at the semantic level (IOperation), and generates `Expression<Func<...>>` factory code using `Expression.*` calls. Registers them in a per-assembly expression registry for runtime lookup.
335
388
336
389
2.**`PolyfillInterceptorGenerator`** — Uses C# 13 method interceptors to replace `ExpressionPolyfill.Create` calls and `IRewritableQueryable<T>` LINQ methods at their call sites, converting lambdas to expression trees at compile time.
Copy file name to clipboardExpand all lines: docs/migration-from-projectables.md
+89-4Lines changed: 89 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -112,11 +112,75 @@ public string? CustomerName => Customer?.Name;
112
112
113
113
| Old Property | Migration |
114
114
|---|---|
115
-
|`UseMemberBody = "SomeMethod"`|Remove — no longer supported. This was typically used to work around syntax limitations in Projectable expression bodies (e.g., pointing to a simpler method when block bodies weren't allowed). Since ExpressiveSharp supports block bodies, switch expressions, pattern matching, and more, you likely don't need it. If you do, please open an issue. |
115
+
|`UseMemberBody = "SomeMethod"`|Replace with `[ExpressiveFor]`. See [Migrating `UseMemberBody`](#migrating-usememberbody) below. |
116
116
|`AllowBlockBody = true`| Remove — block bodies work automatically. `UseExpressives()` registers `FlattenBlockExpressions` globally for EF Core. |
117
117
|`ExpandEnumMethods = true`| Remove — enum method expansion is enabled by default |
118
118
|`CompatibilityMode.Full / .Limited`| Remove — only the full approach exists (query compiler decoration) |
119
119
120
+
### Migrating `UseMemberBody`
121
+
122
+
In Projectables, `UseMemberBody` let you point one member's expression body at another member — typically to work around syntax limitations or to provide an expression-tree-friendly alternative for a member whose actual body couldn't be projected.
123
+
124
+
ExpressiveSharp replaces this with `[ExpressiveFor]` (in the `ExpressiveSharp.Mapping` namespace), which is more explicit and works for external types too.
125
+
126
+
**Scenario 1: Same-type member with an alternative body**
127
+
128
+
```csharp
129
+
// Before (Projectables) — FullName body can't be projected, so use a helper
`[ExpressiveFor]` also enables a use case that Projectables' `UseMemberBody` never supported — providing expression tree bodies for methods on types you don't own:
| Constructors | Not supported |`[ExpressiveForConstructor]`|
181
+
182
+
> **Note:** Many `UseMemberBody` use cases in Projectables existed because of syntax limitations — the projected member's body couldn't use switch expressions, pattern matching, or block bodies. Since ExpressiveSharp supports all of these, you may be able to simply put `[Expressive]` directly on the member and delete the helper entirely.
183
+
120
184
### MSBuild Properties
121
185
122
186
| Old Property | Migration |
@@ -137,7 +201,7 @@ The `InterceptorsNamespaces` MSBuild property needed for method interceptors is
137
201
138
202
4.**`ProjectableOptionsBuilder` callback removed** — `UseProjectables(opts => { ... })` becomes `UseExpressives()` with no parameters. Global transformer configuration is done via `ExpressiveOptions.Default` if needed.
139
203
140
-
5.**`UseMemberBody` property removed** — This was typically a workaround for syntax limitations in Projectable expression bodies. Since ExpressiveSharp supports block bodies, switch expressions, pattern matching, and more, you likely don't need it. Remove any `UseMemberBody` assignments. If your use case still requires it, please [open an issue](https://github.com/EFNext/ExpressiveSharp/issues).
204
+
5.**`UseMemberBody` property removed** — Replaced by `[ExpressiveFor]` from the `ExpressiveSharp.Mapping` namespace. See [Migrating `UseMemberBody`](#migrating-usememberbody).
141
205
142
206
6.**`CompatibilityMode` removed** — ExpressiveSharp always uses the full query-compiler-decoration approach. The `Limited` compatibility mode does not exist.
143
207
@@ -166,6 +230,7 @@ The `InterceptorsNamespaces` MSBuild property needed for method interceptors is
166
230
| Modern syntax in LINQ chains | No | Yes (`IRewritableQueryable<T>`) |
167
231
| Custom transformers | No |`IExpressionTreeTransformer` interface |
168
232
|`ExpressiveDbSet<T>`| No | Yes |
233
+
| External member mapping |`UseMemberBody` (same type only) |`[ExpressiveFor]` (any type, including third-party) |
169
234
| EF Core specific | Yes | No — works standalone |
170
235
| Compatibility modes | Full / Limited | Full only (simpler) |
@@ -271,12 +336,32 @@ public class MyTransformer : IExpressionTreeTransformer
271
336
publicdoubleAdjustedTotal=>Price*Quantity*1.1;
272
337
```
273
338
339
+
### External Member Mapping (`[ExpressiveFor]`)
340
+
341
+
Provideexpression-treebodiesformethodsontypesyoudon't own. This enables using BCL or third-party utility methods in EF Core queries that would otherwise fail with "could not be translated":
0 commit comments