This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
IMPORTANT - Git Commits:
- NEVER automatically commit changes
- NEVER prompt or ask to commit changes
- NEVER suggest creating commits
- The user will handle all git commits manually
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.
dotnet build src --configuration Release# Run all tests except integration tests
dotnet test src --configuration Release --no-build --no-restore --filter Category!=Integration
# Run all tests including integration tests
dotnet test src --configuration Release --no-build --no-restore# Run a specific test by fully qualified name
dotnet test src --filter "FullyQualifiedName~TestNamespace.TestClass.TestMethod"
# Run all tests in a class
dotnet test src --filter "FullyQualifiedName~TestNamespace.TestClass"The README.md and docs/*.md files are auto-generated from source files using MarkdownSnippets. To regenerate documentation:
- Edit the corresponding
*.source.mdfiles in/docs/mdsource/or/readme.source.md - Run MarkdownSnippets to regenerate the markdown files
- Never edit
*.mdfiles directly if they have a "GENERATED FILE - DO NOT EDIT" header
EfGraphQLService (src/GraphQL.EntityFramework/GraphApi/EfGraphQLService*.cs)
- The central service that provides methods to add GraphQL fields backed by EF queries
- Split across multiple partial class files by functionality:
EfGraphQLService_QueryableConnection.cs- Connection (pageable) fields for IQueryableEfGraphQLService_Navigation.cs- Single navigation property fieldsEfGraphQLService_NavigationList.cs- List navigation property fieldsEfGraphQLService_Single.cs- Single entity queries (uses SingleOrDefaultAsync)EfGraphQLService_First.cs- First entity queries (uses FirstOrDefaultAsync)EfGraphQLService_Queryable.cs- General queryable fields
EfObjectGraphType (src/GraphQL.EntityFramework/GraphApi/EfObjectGraphType.cs)
- Base class for entity graph types that provides convenient wrapper methods around EfGraphQLService
- Supports AutoMap() to automatically map entity properties to GraphQL fields
QueryGraphType (base class for root queries)
- Provides entry points for GraphQL queries
- Derived from EfObjectGraphType but serves as the schema root
ArgumentProcessor (src/GraphQL.EntityFramework/GraphApi/ArgumentProcessor.cs)
- Parses GraphQL query arguments (where, orderBy, skip, take, ids) and applies them to IQueryable
- Converts GraphQL filter expressions into EF LINQ queries
ExpressionBuilder (src/GraphQL.EntityFramework/Filters/)
- Builds LINQ expressions from GraphQL where clause arguments
- Supports complex filtering including grouping, negation, and nested properties
ProjectionAnalyzer (src/GraphQL.EntityFramework/GraphApi/ProjectionAnalyzer.cs)
- Analyzes projection expressions to extract required property names
- Used by navigation fields, filters, and FieldBuilder extensions to determine which properties need to be loaded
Filters (src/GraphQL.EntityFramework/Filters/Filters.cs)
- Post-query filtering mechanism for authorization or business rules
- Executed after EF query to determine if nodes should be included in results
- Useful when filter criteria don't exist in the database
The library automatically determines EF includes by interrogating the incoming GraphQL query. When a navigation property is requested in a GraphQL query, the corresponding EF Include is automatically added to the query. This is handled by:
- Examining the GraphQL AST (Abstract Syntax Tree)
- Mapping field names to EF navigation properties
- Building the Include chain (e.g., "Friends.Address")
Field names are uppercased and used as include names by default, but can be overridden with the includeNames parameter.
The library registers services via EfGraphQLConventions.RegisterInContainer<TDbContext>() which:
- Requires an EF
IModelinstance (either passed directly or resolved from container) - Optionally accepts custom DbContext resolver delegate
- Optionally accepts custom Filters resolver delegate
- Supports
disableTrackingflag for AsNoTracking queries
Multiple DbContext types can be registered and used simultaneously:
- Register each with
EfGraphQLConventions.RegisterInContainer<TDbContext1>()andEfGraphQLConventions.RegisterInContainer<TDbContext2>() - Inject
IEfGraphQLService<TDbContext1>andIEfGraphQLService<TDbContext2>separately - Each graph type specifies which DbContext it uses via generic type parameter
Custom DocumentExecuter that uses SerialExecutionStrategy for queries instead of parallel execution. This prevents the "second operation started on context" error that occurs when multiple async fields resolve in parallel using the same DbContext instance.
The library implements the GraphQL Relay connection specification:
AddQueryConnectionField- For pageable root queriesAddNavigationConnectionField- For pageable navigation properties- Supports first/after and last/before pagination
- Includes totalCount, edges, cursor, and pageInfo
Use EfObjectGraphType<TDbContext, TSource> as base class and call:
AddNavigationField- Single navigation propertyAddNavigationListField- Collection navigation propertyAddNavigationConnectionField- Pageable collectionAddQueryField- IQueryable that returns multiple entitiesAddSingleField- IQueryable that returns single entity (throws if multiple)AddFirstField- IQueryable that returns first entity or nullAutoMap()- Automatically map all properties
All query fields support standardized arguments:
ids- Filter by ID(s)where- Complex filtering with comparisons (equal, contains, startsWith, etc.)orderBy- Sorting with path and descending flagskip- Skip N resultstake- Take N results
Arguments are processed in order: ids → where → orderBy → skip → take
The library supports EF projections where you can use Select() to project to DTOs or anonymous types before applying GraphQL field resolution.
The library provides projection-based extension methods on FieldBuilder to safely access navigation properties in custom resolvers:
Extension Methods (src/GraphQL.EntityFramework/GraphApi/FieldBuilderExtensions.cs)
Resolve<TDbContext, TSource, TReturn, TProjection>()- Synchronous resolver with projectionResolveAsync<TDbContext, TSource, TReturn, TProjection>()- Async resolver with projectionResolveList<TDbContext, TSource, TReturn, TProjection>()- List resolver with projectionResolveListAsync<TDbContext, TSource, TReturn, TProjection>()- Async list resolver with projection
Why Use These Methods:
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:
- Storing projection metadata in field metadata
- Compiling the projection expression for runtime execution
- Applying the projection to
context.Sourcebefore calling your resolver - Providing the projected data via
ResolveProjectionContext<TDbContext, TProjection>
Example:
public class ChildGraphType : EfObjectGraphType<IntegrationDbContext, ChildEntity>
{
public ChildGraphType(IEfGraphQLService<IntegrationDbContext> graphQlService) : base(graphQlService) =>
Field<int>("ParentId")
.Resolve<IntegrationDbContext, ChildEntity, int, ParentEntity>(
projection: x => x.Parent!,
resolve: ctx => ctx.Projection.Id);
}The project includes a Roslyn analyzer (GraphQL.EntityFramework.Analyzers) that detects problematic usage patterns at compile time:
GQLEF002: Warns when using Field().Resolve() or Field().ResolveAsync() to access navigation properties without projection
- Category: Usage
- Severity: Warning
- Solution: Use projection-based extension methods instead
Safe Patterns (No Warning):
- Accessing primary key properties (e.g.,
context.Source.Id,context.Source.CompanyIdwhen inCompanyclass) - Accessing foreign key properties (e.g.,
context.Source.ParentId,context.Source.UserId) - Using projection-based extension methods
Unsafe Patterns (Warning):
- Accessing scalar properties (e.g.,
context.Source.Name,context.Source.Age) - these might not be loaded - Accessing navigation properties (e.g.,
context.Source.Parent) - Accessing properties on navigation properties (e.g.,
context.Source.Parent.Id) - Accessing collection navigation properties (e.g.,
context.Source.Children.Count())
Why Only PK/FK Are Safe:
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.
The analyzer automatically runs during build and in IDEs (Visual Studio, Rider, VS Code).
Tests use:
- xUnit v3
- Verify.XunitV3 for snapshot testing
- EfLocalDb for in-memory SQL Server testing
- SQL Server LocalDB for integration tests
The test project (src/Tests/) includes:
IntegrationTests/- Full integration tests with real databaseMultiContextTests/- Tests for multiple DbContext scenarios- Expression and filter unit tests
- Connection/pagination tests
- Uses C# 14+ features (global usings, file-scoped namespaces, record types)
- Implicit usings enabled via Directory.Build.props
- Treats warnings as errors
- Uses .editorconfig for code style enforcement
- Uses Fody/ConfigureAwait.Fody for ConfigureAwait(false) injection
- Always use
_ => _for single parameter delegates (notx => xor other named parameters)