I started working on the official “Getting started with GraphQL on ASP.NET Core and Hot Chocolate – Workshop” tutorial, but since the workshop is a bit outdated, it targets .NET 5 and an older version of Hot Chocolate. I am using the latest .NET 7 (at the time of this writing) and top-level statements. In this post, I will address the issues I ran into due to Hot Chocolate version changes from 11 to 13.
Hot Chocolate is a powerful .NET GraphQL server I have wanted to learn about for some time.
Issues and Other Changes
Lesson 1
In the latest .NET and top-level statements there is no Startup.cs
file, so whenever they mention adding something to Startup.cs
file, add it to Program.cs
.
To configure the GraphQL middleware just add app.MapGraphQL();
var app = builder.Build(); app.MapGraphQL(); app.MapGet("/", () => "Hello World!"); app.Run();
Lesson 3
To support pooled DB context, besides replacing services.AddDbContext
with services.AddPooledDbContextFactory
as the guide states, you will need to register the ApplicationDbContext
like this: services.RegisterDbContext(DbContextKind.Pooled)
. Also, make sure to register the context after services.AddGraphQLserver()
.
services .AddPooledDbContextFactory<ApplicationDbContext>(options => options.UseSqlite("Data Source=conferences.db") ) .AddGraphQLServer() .RegisterDbContext<ApplicationDbContext>(DbContextKind.Pooled)
Remove [ScopedService]
annotation, it is obsolete. Registering the DB context as outlined above replaces this annotation.
The error message I got before doing the above:
{ "message": "Unexpected Execution Error", "locations": [ { "line": 25, "column": 3 } ], "path": [ "a" ], "extensions": { "message": "No service for type 'ConferencePlanner.GraphQL.Data.ApplicationDbContext' has been registered.", "stackTrace": "<<OMITTED FOR BREVITY>>" } },
When adding the SpeakerByIdDataLoader
class, make the BatchDataLoader
TValue
– Speaker
– nullable.
public class SpeakerByIdDataLoader : BatchDataLoader<int, Speaker?> { // <<omitted for brevity>> protected override async Task<IReadOnlyDictionary<int, Speaker?>> LoadBatchAsync ( // <<omitted for brevity>> ) { // <<omitted for brevity>> } }
Not making Speaker
nullable will result in an error when searching for a specific speaker id that does not exist in the database
{ "errors": [ { "message": "Cannot return null for non-nullable field.", "locations": [ { "line": 46, "column": 3 } ], "path": [ "b" ], "extensions": { "code": "HC0018" } } ] }
Basically making the return type in the GraphQL schema definition as not required.
type Query { speakers: [Speaker!]! speaker(id: Int!): Speaker }
When adding the SpeakerType
class, you will need to add a [Parent]
annotation to the Speaker
parameter of SpeakerResolvers.GetSessionsAsync
method.
private class SpeakerResolvers { public async Task<IEnumerable<Session>> GetSessionsAsync ( [Parent] Speaker speaker, ApplicationDbContext dbContext, SessionByIdDataLoader sessionById, CancellationToken cancellationToken ) { // <<omitted for brevity>> } }
To fix the following error:
{ "message": "There was no argument with the name `speaker` found on the field `sessions`.", "locations": [ { "line": 56, "column": 9 } ], "path": [ "speakers", 3, "sessions" ], "extensions": { "fieldName": "sessions", "argumentName": "speaker" } },
References:
- https://chillicream.com/docs/hotchocolate/v13/integrations/entity-framework
- https://chillicream.com/docs/hotchocolate/v13/migrating/migrate-from-11-to-12
- https://chillicream.com/docs/hotchocolate/v13/migrating/migrate-from-12-to-13
Lesson 4
To enable Relay support, replace services.EnableRelaySupport()
with services.AddGlobalObjectIdentification()
. The former has been deprecated.
When creating *Input
records with parameters as global identifiers ([ID]
), do not write them as parameters, but rather as properties.
// ======= Instead of this ======= public record AddSessionInput( string Title, string? Abstract, [ID(nameof(Speaker))] IReadOnlyList<int> SpeakerIds ); // ======= Do this ======= public record AddSessionInput { public string Title { get; set; } public string? Abstract { get; set; } [ID(nameof(Speaker))] public IReadOnlyList<int> SpeakerIds { get; set; } }
The global identification annotations do not work as parameters, only as properties when used this way. In the first instance above, the GraphQL compiled schema will take int
s as inputs, as opposed to the generated base64 global identifier.
Generated schemas
input AddSessionInput { title: String! abstract: String speakerIds: [int!]! # <== Speaker ids as ints } input AddSessionInput { title: String! abstract: String speakerIds: [ID!]! # <== Speaker ids as global identifiers (ID) }
Records that should be changed as outlined above:
- AddSessionInput
- RenameTrackInput
- ScheduleSessionInput
References:
Lesson 7
Rewrite CheckInAttendeeInput
record to have properties instead of parameters.
public record CheckInAttendeeInput { [ID(nameof(Session))] public int SessionId { get; set; } [ID(nameof(Attendee))] public int AttendeeId { get; set; } }
Lesson 8
Register the DB Context after adding GraphQL for each test case
.AddPooledDbContextFactory<ApplicationDbContext>( options => options.UseInMemoryDatabase("Data Source=conferences.db") ) .AddGraphQL() .RegisterDbContext<ApplicationDbContext>(DbContextKind.Pooled) // <<OMITTED FOR BREVITY>> .BuildSchemaAsync();
The test cases will fail with the following error message if RegisterDbContext
is missing.
HotChocolate.SchemaException: For more details look at the `Errors` property. HotChocolate.SchemaException For more details look at the `Errors` property. 1. Unable to infer or resolve a schema type from the type reference `IDbContextTransaction (Input)`. (HotChocolate.Types.InputObjectType<Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade>) 2. Unable to infer or resolve a schema type from the type reference `IModel (Input)`. <<STACK TRACE OMITTED FOR BREVITY>>
Final Code Repo
I have created the following GitHub repo with the final workshop code. Hopefully, you find it useful.
https://github.com/esausilva/hot-chocolate-graphql-workshop-updated
Conclusion
Well, I had fun working my way through the workshop and figuring out the issues with newer versions of Hot Chocolate and .NET 7. I just wish the maintainers would update the workshop.
If you liked this tutorial, share it on your social media and you can follow me on Twitter or LinkedIn.
Consider giving back by getting me a coffee (or a couple) by clicking the following button: