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
{{ message }}
Repository navigation
Commit ad1718a
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/guide/migration-from-projectables.md
+7-5Lines changed: 7 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -161,10 +161,12 @@ public string FullName
161
161
162
162
See the [Projectable Properties reference](../reference/projectable-properties) and the [Projection Middleware recipe](../recipes/projection-middleware) for the complete feature.
163
163
164
-
**Option B -- `[ExpressiveFor]`** (separate stub, supports cross-type mapping):
164
+
**Option B -- `[ExpressiveFor]`** (separate stub, also supports cross-type mapping):
165
165
166
166
**Scenario 1: Same-type member with an alternative body**
167
167
168
+
Use the co-located form: a property stub on the same class combined with the single-argument attribute. `this` is the receiver naturally -- the migration reads almost identically to `UseMemberBody`.
Copy file name to clipboardExpand all lines: docs/recipes/external-member-mapping.md
+27-3Lines changed: 27 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -191,23 +191,47 @@ static class DateTimeMappings
191
191
|------|-------------|
192
192
| EXP0014 |`[ExpressiveFor]` target type not found |
193
193
| EXP0015 |`[ExpressiveFor]` target member not found on the specified type |
194
-
| EXP0016 |`[ExpressiveFor]` stub method must be `static`|
195
194
| EXP0017 | Return type of stub does not match target member's return type |
196
195
| EXP0019 | Target member already has `[Expressive]` -- use `[Expressive]` directly instead |
197
196
| EXP0020 | Duplicate `[ExpressiveFor]` mapping for the same target member |
198
197
199
198
## Tips
200
199
201
200
::: tip Match the signature exactly
202
-
For static methods, the stub parameters must match the target method signature. For instance members, add the receiver as the first parameter (e.g., `static string FullName(Person p)`).
201
+
For static methods, the stub parameters must match the target method signature. For instance members you have three options: write a `static` method stub whose first parameter is the receiver (e.g. `static string FullName(Person p)`), write an `instance` method stub on the target type itself where the stub's `this` is the receiver, or write a **property stub** on the target type (parameterless; `this` is the receiver) -- property stubs can only target other properties.
202
+
:::
203
+
204
+
::: tip Co-locate when possible
205
+
When the target is on the same type as the stub, you can drop `typeof(...)` and use the single-argument form -- it targets a member on the stub's containing type. Combined with an **instance property stub**, this is the ergonomic shape for the case where a property has its own backing storage (a plain settable auto-property used for DTO shape, serialization, or in-memory assignment in tests) but you want queries to compute it from other columns:
206
+
207
+
```csharp
208
+
publicclassPerson
209
+
{
210
+
publicstringFirstName { get; set; } ="";
211
+
publicstringLastName { get; set; } ="";
212
+
213
+
// Regular auto-property — can be assigned directly (for DTOs, tests, deserialization).
214
+
publicstringFullName { get; set; } ="";
215
+
216
+
// When used in a LINQ expression tree, FullName is rewritten to this body,
217
+
// so EF Core projects it from the underlying columns instead of trying to
218
+
// map it to a column of its own. `this` is the receiver automatically.
A method stub (`string FullNameExpression() => ...`) works the same way and is appropriate when the target is a method or you need a block body.
225
+
226
+
If the property has no backing storage and the same body works at both runtime and query time, put `[Expressive]` directly on the property and delete the stub.
203
227
:::
204
228
205
229
::: tip Consider [Expressive] first
206
230
Many `[ExpressiveFor]` use cases exist because of syntax limitations in other libraries. Since ExpressiveSharp supports switch expressions, pattern matching, string interpolation, and more, you may be able to put `[Expressive]` directly on the member and skip the external mapping entirely.
207
231
:::
208
232
209
233
::: warning Placement
210
-
`[ExpressiveFor]` stubs must be in a `static` class. The class can be in any namespace -- it is discovered at compile time by the source generator.
234
+
`[ExpressiveFor]` stubs can live either in a `static`helper class (with a `static` method stub) or on the target type itself as an instance method or property. Either way, they are discovered at compile time by the source generator.
Copy file name to clipboardExpand all lines: docs/reference/diagnostics.md
+4-27Lines changed: 4 additions & 27 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,10 @@ The ExpressiveSharp source generator and companion analyzers emit diagnostics du
6
6
See [Troubleshooting](./troubleshooting) for symptom-oriented guidance -- find the error message or behavior you see and get step-by-step resolution.
7
7
:::
8
8
9
+
::: info Retired diagnostics
10
+
`EXP0016` ("`[ExpressiveFor]` stub must be static") has been retired. Instance stubs on the target type are now permitted; constructor-stub and unrelated-type mismatches surface as `EXP0015` (member not found) instead.
11
+
:::
12
+
9
13
## Overview
10
14
11
15
| ID | Severity | Title | Code Fix |
@@ -25,7 +29,6 @@ See [Troubleshooting](./troubleshooting) for symptom-oriented guidance -- find t
25
29
|[EXP0013](#exp0013)| Warning | Member could benefit from `[Expressive]`|[Add `[Expressive]`](#exp0013-fix)|
26
30
|[EXP0014](#exp0014)| Error |`[ExpressiveFor]` target type not found | -- |
27
31
|[EXP0015](#exp0015)| Error |`[ExpressiveFor]` target member not found | -- |
28
-
|[EXP0016](#exp0016)| Error |`[ExpressiveFor]` stub must be static | -- |
29
32
|[EXP0017](#exp0017)| Error |`[ExpressiveFor]` return type mismatch | -- |
30
33
|[EXP0019](#exp0019)| Error |`[ExpressiveFor]` conflicts with `[Expressive]`| -- |
Copy file name to clipboardExpand all lines: docs/reference/expressive-for.md
+57-7Lines changed: 57 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,15 +10,21 @@ using ExpressiveSharp.Mapping;
10
10
11
11
## How It Works
12
12
13
-
You write a static stub method whose body defines the expression-tree replacement. The `[ExpressiveFor]` attribute tells the generator which external member this stub maps to. At runtime, the replacer substitutes calls to the target member with the stub's expression tree -- call sites remain unchanged.
13
+
You write a stub member -- a method**or** a property -- whose body defines the expression-tree replacement. The `[ExpressiveFor]` attribute tells the generator which external member this stub maps to. At runtime, the replacer substitutes calls to the target member with the stub's expression tree -- call sites remain unchanged.
14
14
15
15
## Mapping Rules
16
16
17
-
- The stub method **must be `static`** (EXP0016 if not).
18
-
- For **static methods**, the stub's parameters must match the target method's parameters exactly.
19
-
- For **instance methods**, the first parameter of the stub is the receiver (`this`), followed by the target method's parameters.
20
-
- For **instance properties**, the stub takes a single parameter: the receiver.
21
-
- The return type must match (EXP0017 if not).
17
+
- The stub can be a **method** (receiver supplied as the first parameter for instance targets, or `this` for instance stubs on the target type) **or** a **property** (parameterless; `this` is the receiver for instance stubs).
18
+
- The single-argument form `[ExpressiveFor(nameof(X))]` is shorthand for `[ExpressiveFor(typeof(ContainingType), nameof(X))]` -- use it when the target member is on the same type as the stub.
19
+
- For **static methods** (and static stubs over static members), the stub's parameters must match the target method's parameters exactly.
20
+
- For **instance methods** with a `static` stub, the first parameter of the stub is the receiver (`this`), followed by the target method's parameters.
21
+
- For **instance methods** with an `instance` stub on the target type, `this` is the receiver; remaining parameters match the target's exactly.
22
+
- For **instance properties** with a `static` method stub, the stub takes a single parameter: the receiver.
23
+
- For **instance properties** with an `instance` method or property stub on the target type, the stub is parameterless.
24
+
- For **static properties**, the stub is parameterless.
25
+
- Property stubs can only target other properties (no parameters to carry method arguments).
26
+
- The return type / property type must match (EXP0017 if not).
27
+
- Constructor stubs (`[ExpressiveForConstructor]`) must still be `static` methods; instance or property ctor stubs have no coherent meaning.
22
28
23
29
## Static Method Mapping
24
30
@@ -63,10 +69,55 @@ static class EntityMappings
63
69
}
64
70
```
65
71
72
+
## Co-located Form (Instance Stub + Single-argument Attribute)
73
+
74
+
When the target is on the same type as the stub, the most ergonomic form combines an **instance stub** with the **single-argument** attribute. `this` is the receiver automatically. Use this form when a property has its own backing storage -- e.g. a plain settable auto-property used for DTO shape, serialization, or in-memory assignment in tests -- but queries should still compute it from other columns.
75
+
76
+
A **property stub** is often the cleanest choice for this (no parentheses, reads like the target it replaces):
Both forms are equivalent to the verbose `[ExpressiveFor(typeof(Person), nameof(Person.FullName))] static string FullName(Person obj) => obj.FirstName + " " + obj.LastName;` but reuse `this` instead of threading a receiver parameter. When the EF Core integration is enabled, both the target property **and** the stub property itself are automatically excluded from the model (no `[NotMapped]` needed -- see [Automatic NotMapped for `[ExpressiveFor]` targets](#automatic-notmapped-for-expressivefor-targets)).
102
+
103
+
::: warning When to prefer `[Expressive]` instead
104
+
If the property has no backing storage and the same body works at both runtime and query time, put `[Expressive]` directly on it (`[Expressive] public string FullName => FirstName + " " + LastName;`) and skip the stub. `[ExpressiveFor]` is for the dual-body case; `[Expressive]` is for the single-body case.
105
+
:::
106
+
66
107
::: tip
67
108
The stub can use any C# syntax that `[Expressive]` supports -- switch expressions, pattern matching, null-conditional operators, and more.
68
109
:::
69
110
111
+
## Automatic NotMapped for `[ExpressiveFor]` targets
112
+
113
+
When `UseExpressives()` is active, EF Core's model builder automatically ignores properties that are:
114
+
115
+
1. Decorated with `[Expressive]`,
116
+
2. Decorated with `[ExpressiveFor]` (a property stub itself), **or**
117
+
3. The target of an `[ExpressiveFor]` stub anywhere in the loaded assemblies.
118
+
119
+
You do not need to add `[NotMapped]` to a property you are expressing externally or using as a property stub -- the `ExpressivePropertiesNotMappedConvention` detects these cases via attribute metadata and the generated registry and calls `Ignore()` for you.
120
+
70
121
## Constructor Mapping with `[ExpressiveForConstructor]`
71
122
72
123
Use `[ExpressiveForConstructor]` to provide an expression-tree body for a constructor on a type you do not own:
@@ -114,7 +165,6 @@ The following diagnostics are specific to `[ExpressiveFor]` and `[ExpressiveForC
114
165
|------|----------|-------------|
115
166
|[EXP0014](./diagnostics#exp0014)| Error | Target type specified in `[ExpressiveFor]` could not be resolved |
116
167
|[EXP0015](./diagnostics#exp0015)| Error | No member with the given name found on the target type matching the stub's parameter signature |
117
-
|[EXP0016](./diagnostics#exp0016)| Error | The stub method must be `static`|
118
168
|[EXP0017](./diagnostics#exp0017)| Error | Return type of the stub does not match the target member's return type |
119
169
|[EXP0019](./diagnostics#exp0019)| Error | The target member already has `[Expressive]` -- remove one of the two attributes |
120
170
|[EXP0020](./diagnostics#exp0020)| Error | Duplicate mapping -- only one stub per target member is allowed |
Copy file name to clipboardExpand all lines: src/ExpressiveSharp.EntityFrameworkCore/Infrastructure/Internal/ExpressivePropertiesNotMappedConvention.cs
0 commit comments