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: claude.md
+67Lines changed: 67 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,14 @@
2
2
3
3
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
4
5
+
## Workflow Guidelines
6
+
7
+
**IMPORTANT - Git Commits:**
8
+
- NEVER automatically commit changes
9
+
- NEVER prompt or ask to commit changes
10
+
- NEVER suggest creating commits
11
+
- The user will handle all git commits manually
12
+
5
13
## Project Overview
6
14
7
15
GraphQL.EntityFramework is a .NET library that adds EntityFramework Core IQueryable support to GraphQL.NET. It enables automatic query generation, filtering, pagination, and ordering for GraphQL queries backed by EF Core.
@@ -67,6 +75,10 @@ The README.md and docs/*.md files are auto-generated from source files using [Ma
67
75
- Builds LINQ expressions from GraphQL where clause arguments
68
76
- Supports complex filtering including grouping, negation, and nested properties
-`Resolve<TDbContext, TSource, TReturn, TProjection>()` - Synchronous resolver with projection
157
+
-`ResolveAsync<TDbContext, TSource, TReturn, TProjection>()` - Async resolver with projection
158
+
-`ResolveList<TDbContext, TSource, TReturn, TProjection>()` - List resolver with projection
159
+
-`ResolveListAsync<TDbContext, TSource, TReturn, TProjection>()` - Async list resolver with projection
160
+
161
+
**Why Use These Methods:**
162
+
When using `Field().Resolve()` or `Field().ResolveAsync()` directly, navigation properties on `context.Source` may be null if the projection system didn't include them. The projection-based extension methods ensure required data is loaded by:
163
+
1. Storing projection metadata in field metadata
164
+
2. Compiling the projection expression for runtime execution
165
+
3. Applying the projection to `context.Source` before calling your resolver
166
+
4. Providing the projected data via `ResolveProjectionContext<TDbContext, TProjection>`
The EF projection system always loads primary keys and foreign keys, but other properties (including regular scalars like `Name` or `Age`) are only loaded if explicitly requested in the GraphQL query. Accessing them in a resolver without projection can cause null reference exceptions.
202
+
203
+
The analyzer automatically runs during build and in IDEs (Visual Studio, Rider, VS Code).
204
+
139
205
## Testing
140
206
141
207
Tests use:
@@ -157,3 +223,4 @@ The test project (`src/Tests/`) includes:
157
223
- Treats warnings as errors
158
224
- Uses .editorconfig for code style enforcement
159
225
- Uses Fody/ConfigureAwait.Fody for ConfigureAwait(false) injection
226
+
- Always use `_ => _` for single parameter delegates (not `x => x` or other named parameters)
Copy file name to clipboardExpand all lines: docs/defining-graphs.md
+80-7Lines changed: 80 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -46,7 +46,29 @@ context.Heros
46
46
.Include("Friends.Address");
47
47
```
48
48
49
-
The string for the include is taken from the field name when using `AddNavigationField` or `AddNavigationConnectionField` with the first character upper cased. This value can be overridden using the optional parameter `includeNames` . Note that `includeNames` is an `IEnumerable<string>` so that multiple navigation properties can optionally be included for a single node.
49
+
The string for the include is taken from the field name when using `AddNavigationField` or `AddNavigationConnectionField` with the first character upper cased. This can be customized using a projection expression:
50
+
51
+
```cs
52
+
// Using projection to specify which navigation properties to include
53
+
AddNavigationConnectionField(
54
+
name: "employeesConnection",
55
+
projection: _=>_.Employees,
56
+
resolve: ctx=>ctx.Projection);
57
+
58
+
// Multiple properties can be included using anonymous types
59
+
AddNavigationField(
60
+
name: "child1",
61
+
projection: _=>new { _.Child1, _.Child2 },
62
+
resolve: ctx=>ctx.Projection.Child1);
63
+
64
+
// Nested navigation paths are also supported
65
+
AddNavigationField(
66
+
name: "level3Entity",
67
+
projection: _=>_.Level2Entity.Level3Entity,
68
+
resolve: ctx=>ctx.Projection);
69
+
```
70
+
71
+
The projection expression provides type-safety and automatically extracts the include names from the accessed properties.
50
72
51
73
52
74
## Projections
@@ -146,6 +168,55 @@ public class OrderGraph :
146
168
Without automatic foreign key inclusion, `context.Source.CustomerId` would be `0` (or `Guid.Empty` for Guid keys) if `customerId` wasn't explicitly requested in the GraphQL query, causing the query to fail.
147
169
148
170
171
+
### Using Projection-Based Resolve
172
+
173
+
When using `Field().Resolve()` or `Field().ResolveAsync()` in graph types, navigation properties on `context.Source` may not be loaded if the projection system didn't include them. To safely access navigation properties in custom resolvers, use the projection-based extension methods:
resolve: ctx=>ctx.Projection.Id); // Parent is guaranteed to be loaded
215
+
```
216
+
217
+
**Note:** A Roslyn analyzer (GQLEF002) warns at compile time when `Field().Resolve()` accesses properties other than primary keys and foreign keys. Only PK and FK properties are guaranteed to be loaded by the projection system - all other properties (including regular scalars like `Name`) require projection-based extension methods to ensure they are loaded.
218
+
219
+
149
220
### When Projections Are Not Used
150
221
151
222
Projections are bypassed and the full entity is loaded in these cases:
@@ -213,16 +284,17 @@ public class CompanyGraph :
213
284
{
214
285
AddNavigationListField(
215
286
name: "employees",
216
-
resolve: _=>_.Source.Employees);
287
+
projection: _=>_.Employees,
288
+
resolve: _=>_.Projection);
217
289
AddNavigationConnectionField(
218
290
name: "employeesConnection",
219
-
resolve: _=>_.Source.Employees,
220
-
includeNames: ["Employees"]);
291
+
projection: _=>_.Employees,
292
+
resolve: _=>_.Projection);
221
293
AutoMap();
222
294
}
223
295
}
224
296
```
225
-
<sup><ahref='/src/Snippets/TypedGraph.cs#L5-L24'title='Snippet source file'>snippet source</a> | <ahref='#snippet-typedGraph'title='Start of snippet'>anchor</a></sup>
297
+
<sup><ahref='/src/Snippets/TypedGraph.cs#L5-L25'title='Snippet source file'>snippet source</a> | <ahref='#snippet-typedGraph'title='Start of snippet'>anchor</a></sup>
226
298
<!-- endSnippet -->
227
299
228
300
@@ -340,10 +412,11 @@ public class CompanyGraph :
340
412
base(graphQlService) =>
341
413
AddNavigationConnectionField(
342
414
name: "employees",
343
-
resolve: _=>_.Source.Employees);
415
+
projection: _=>_.Employees,
416
+
resolve: _=>_.Projection);
344
417
}
345
418
```
346
-
<sup><ahref='/src/Snippets/ConnectionTypedGraph.cs#L5-L17'title='Snippet source file'>snippet source</a> | <ahref='#snippet-ConnectionTypedGraph'title='Start of snippet'>anchor</a></sup>
419
+
<sup><ahref='/src/Snippets/ConnectionTypedGraph.cs#L5-L18'title='Snippet source file'>snippet source</a> | <ahref='#snippet-ConnectionTypedGraph'title='Start of snippet'>anchor</a></sup>
Copy file name to clipboardExpand all lines: docs/mdsource/defining-graphs.source.md
+72-1Lines changed: 72 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,7 +39,29 @@ context.Heros
39
39
.Include("Friends.Address");
40
40
```
41
41
42
-
The string for the include is taken from the field name when using `AddNavigationField` or `AddNavigationConnectionField` with the first character upper cased. This value can be overridden using the optional parameter `includeNames` . Note that `includeNames` is an `IEnumerable<string>` so that multiple navigation properties can optionally be included for a single node.
42
+
The string for the include is taken from the field name when using `AddNavigationField` or `AddNavigationConnectionField` with the first character upper cased. This can be customized using a projection expression:
43
+
44
+
```cs
45
+
// Using projection to specify which navigation properties to include
46
+
AddNavigationConnectionField(
47
+
name: "employeesConnection",
48
+
projection: _=>_.Employees,
49
+
resolve: ctx=>ctx.Projection);
50
+
51
+
// Multiple properties can be included using anonymous types
52
+
AddNavigationField(
53
+
name: "child1",
54
+
projection: _=>new { _.Child1, _.Child2 },
55
+
resolve: ctx=>ctx.Projection.Child1);
56
+
57
+
// Nested navigation paths are also supported
58
+
AddNavigationField(
59
+
name: "level3Entity",
60
+
projection: _=>_.Level2Entity.Level3Entity,
61
+
resolve: ctx=>ctx.Projection);
62
+
```
63
+
64
+
The projection expression provides type-safety and automatically extracts the include names from the accessed properties.
Without automatic foreign key inclusion, `context.Source.CustomerId` would be `0` (or `Guid.Empty` for Guid keys) if `customerId` wasn't explicitly requested in the GraphQL query, causing the query to fail.
88
110
89
111
112
+
### Using Projection-Based Resolve
113
+
114
+
When using `Field().Resolve()` or `Field().ResolveAsync()` in graph types, navigation properties on `context.Source` may not be loaded if the projection system didn't include them. To safely access navigation properties in custom resolvers, use the projection-based extension methods:
resolve: ctx=>ctx.Projection.Id); // Parent is guaranteed to be loaded
156
+
```
157
+
158
+
**Note:** A Roslyn analyzer (GQLEF002) warns at compile time when `Field().Resolve()` accesses properties other than primary keys and foreign keys. Only PK and FK properties are guaranteed to be loaded by the projection system - all other properties (including regular scalars like `Name`) require projection-based extension methods to ensure they are loaded.
159
+
160
+
90
161
### When Projections Are Not Used
91
162
92
163
Projections are bypassed and the full entity is loaded in these cases:
0 commit comments