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 07cde84
Browse filesBrowse the repository at this point in the historyBrowse files
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 [Projectable properties](../../reference/projectable-properties), 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-for#synthesizing-a-property-with-synthesize-true), 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/migration-from-projectables.md
+16-17Lines changed: 16 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -122,7 +122,7 @@ Both the old `Ignore` and `Rewrite` behaviors converge to the same result in Exp
122
122
123
123
| Old Property | Migration |
124
124
|---|---|
125
-
|`UseMemberBody = "SomeMethod"`| Replace with `[Expressive(Projectable = true)]` or `[ExpressiveFor]`. See [Migrating UseMemberBody](#migrating-usememberbody) below. |
125
+
|`UseMemberBody = "SomeMethod"`| Replace with `[ExpressiveProperty]` or plain`[ExpressiveFor]`. See [Migrating UseMemberBody](#migrating-usememberbody) below. |
126
126
|`AllowBlockBody = true`| Keep -- block bodies remain opt-in. Set per-member or globally via `Expressive_AllowBlockBody` MSBuild property. |
127
127
|`ExpandEnumMethods = true`| Remove -- enum method expansion is enabled by default. |
128
128
|`CompatibilityMode.Full / .Limited`| Remove -- only the full approach exists. |
@@ -131,37 +131,36 @@ Both the old `Ignore` and `Rewrite` behaviors converge to the same result in Exp
131
131
132
132
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 projection middleware (HotChocolate, AutoMapper) that required a writable target.
133
133
134
-
ExpressiveSharp offers **two replacement options**, depending on your scenario:
134
+
ExpressiveSharp offers **two replacement shapes**, depending on your scenario:
135
135
136
-
-**`[Expressive(Projectable = true)]`** -- the ergonomic fit when your goal was specifically to participate in projection middleware. Keeps the formula on the property itself via the `field ?? (<formula>)` pattern. Closest to Projectables' intent.
137
-
-**`[ExpressiveFor]`** -- the verbose but explicit alternative. Works for external types too (scenarios `UseMemberBody` never supported).
136
+
-**`[ExpressiveProperty]`** -- the closest analogue: you write only the formula; the generator synthesizes the settable target property on a `partial` class. The property participates in projection middleware because it has an `init` accessor. Best fit when you want a dedicated property backed purely by an expression.
137
+
-**Plain `[ExpressiveFor]`** -- when the target property already exists (or lives on an external type you do not own). No property is synthesized; the stub maps to an existing member.
138
138
139
-
Either is correct; pick based on ergonomic preference and whether you need the cross-type capability.
139
+
Pick based on whether you want the generator to declare the target property for you.
140
140
141
-
::: info About the `Projectable` name overlap
142
-
ExpressiveSharp's `Projectable` attribute property and the EFCore.Projectables library's `[Projectable]` attribute share a name because both describe the same capability -- a computed property that participates in LINQ projections. They are different mechanisms; the shared word is intentional to reduce migration friction.
143
-
:::
144
-
145
-
**Option A -- `[Expressive(Projectable = true)]`** (single declaration):
141
+
**Option A -- `[ExpressiveProperty]`** (formula-only, property is generated):
See the [Projectable Properties reference](../reference/projectable-properties) and the [Projection Middleware recipe](../recipes/projection-middleware) for the complete feature.
157
+
The generator picks between a coalesce shape (non-nullable targets) and a ternary+flag shape (nullable targets) so materialized `null` stays distinguishable from "not materialized." See the [`[ExpressiveProperty]` reference](../reference/expressive-property) and the [Projection Middleware recipe](../recipes/projection-middleware).
158
+
159
+
::: warning Target name must be a string literal
160
+
The target property does not exist during the generator's pass, so `nameof(FullName)` fails to resolve. Always pass the name as a string literal: `[ExpressiveProperty("FullName")]`.
161
+
:::
163
162
164
-
**Option B -- `[ExpressiveFor]`** (separate stub, also supports cross-type mapping):
163
+
**Option B -- plain `[ExpressiveFor]`** (target property already exists, or lives on an external type):
165
164
166
165
**Scenario 1: Same-type member with an alternative body**
Copy file name to clipboardExpand all lines: docs/recipes/projection-middleware.md
+29-39Lines changed: 29 additions & 39 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,11 +14,11 @@ HotChocolate inspects the `User.FullName` property, finds it is **read-only** (n
14
14
15
15
The same mechanism affects AutoMapper's `ProjectTo<Entity>`, Mapperly's generated projections, and any hand-rolled `Select(u => new User { ... })` that projects into the source type itself.
16
16
17
-
## The fix: `[Expressive(Projectable = true)]`
17
+
## The fix: `[ExpressiveProperty]`
18
18
19
-
Turning on `Projectable = true` makes the property writable (so the projection middleware emits a binding) while still registering the formula for SQL translation. The dual-direction runtime behavior is:
19
+
Write the formula as a stub on a `partial` class and let the generator synthesize a settable property for you. The synthesized property is writable (so the projection middleware emits a binding) while still registering the formula for SQL translation. The dual-direction runtime behavior is:
20
20
21
-
-**In memory**, reading the property evaluates the formula from dependencies (same as plain `[Expressive]`).
21
+
-**In memory**, reading the property evaluates the stub formula from dependencies (same as plain `[Expressive]`).
22
22
-**After materialization from SQL**, reading the property returns the stored value (which the middleware's binding wrote via the `init` accessor).
SQL emitted: `SELECT u.LastName || ', ' || u.FirstName AS "FullName" FROM Users u` -- formula pushed into SQL.
61
59
62
-
No HC glue code is required beyond the normal `.UseExpressives()` on the DbContext options. The convention auto-ignores the property in EF's model (so no `FullName` column is created), and the projection rewrite happens automatically when the query compiler intercepts.
60
+
No HC glue code is required beyond the normal `.UseExpressives()` on the DbContext options. The convention auto-ignores the synthesized property in EF's model (so no `FullName` column is created), and the projection rewrite happens automatically when the query compiler intercepts.
61
+
62
+
::: warning Target name must be a string literal
63
+
`nameof(FullName)` won't resolve here because `FullName` doesn't exist until the generator emits it. Pass the name as a string literal: `[ExpressiveProperty("FullName")]`.
Notice how `DisplayLabel` composes with `FullName` (which is itself Projectable) -- the transitive rewrite is handled automatically by the expression resolver.
115
+
Notice how `DisplayLabel` composes with `FullName` (which is itself synthesized) -- the transitive rewrite is handled automatically by the expression resolver.
122
116
123
117
## Full AutoMapper example
124
118
125
-
AutoMapper's `ProjectTo<T>()` emits the same `new T { ... }` pattern as HotChocolate, so Projectable members work the same way:
119
+
AutoMapper's `ProjectTo<T>()` emits the same `new T { ... }` pattern as HotChocolate, so synthesized members work the same way:
126
120
127
121
```csharp
128
122
varconfig=newMapperConfiguration(cfg=>
@@ -140,30 +134,26 @@ var users = await db.Users
140
134
// FROM Users u
141
135
```
142
136
143
-
## When to use `[ExpressiveFor]` instead
137
+
## When the target property already exists
144
138
145
-
If your class can't use the C# 14 `field` keyword, or you want to keep the formula in a separate mapping class, the [`[ExpressiveFor]`](../reference/expressive-for) pattern is a verbose alternative:
139
+
If you already have a settable auto-property (e.g. because it is used for DTO shape or deserialization), use the plain `[ExpressiveFor]` form:
Both approaches produce the same SQL and work identically with HotChocolate / AutoMapper. The `Projectable` form is more concise and keeps the formula on the property itself; the `ExpressiveFor` form is explicit about the separation. See the [migration guide](../guide/migration-from-projectables) for a side-by-side comparison.
153
+
The two forms produce the same SQL behaviour; the difference is who declares the target property. Use `[ExpressiveProperty]` when the property exists only to support projection middleware; use plain `[ExpressiveFor]` when the property has its own reason to exist.
163
154
164
155
## See Also
165
156
166
-
-[Projectable Properties](../reference/projectable-properties) -- full reference including restrictions and runtime semantics
167
-
-[`[ExpressiveFor]` Mapping](../reference/expressive-for) -- alternative pattern for scenarios where you can't modify the entity type
157
+
-[`[ExpressiveProperty]` Attribute](../reference/expressive-property) -- full reference
168
158
-[Migrating from Projectables](../guide/migration-from-projectables) -- side-by-side migration paths for `UseMemberBody`
0 commit comments