graphql-dotnet: Error trying to resolve field
I’m derp!
The issue is that EntityFramework is too smart and too forgiving, but GraphQL is not forgiving. đź’Ş
var applicationString = builder.Configuration.GetConnectionString("database") ?? throw new InvalidOperationException("'database' not found.");
builder.Services.AddDbContext<AppDbContext>(options => { options.UseSqlServer(applicationString); }, ServiceLifetime.Singleton);
Check the edit history kthxbai
Kind Regards,
jee-mj
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 19 (12 by maintainers)
As for the data loader, the instructions you are following are out of date. The call to
AddDataLoaderhere:https://github.com/jee-mj/net6-graphql-ef-api/blob/main/Program.cs#L43
Already performs the work of these lines:
https://github.com/jee-mj/net6-graphql-ef-api/blob/main/Program.cs#L31-L32
It also adds the document listener to the execution. For reference, see the source of the
AddDataLoadermethod:https://github.com/graphql-dotnet/graphql-dotnet/blob/master/src/GraphQL.DataLoader/Extensions/DataLoaderGraphQLBuilderExtensions.cs#L16-L20
And the source of the
AddDocumentListenermethod:https://github.com/graphql-dotnet/graphql-dotnet/blob/master/src/GraphQL/Extensions/GraphQLBuilderExtensions.cs#L640-L646
I didn’t mention it before because the extra registration should have no effect.
Just FYI, you can also remove
builder.Services.AddSingleton<ISchema, ApiSchema>();because the schema is registered within the call to.AddSchema<ApiSchema>(). And if you want to remove the calls to registerApiQueryandRegistrationType, just add a call to.AddGraphTypes()within your Program.cs, which will scan the calling assembly for graph types and register them with your DI provider.Just FYI, Entity Framework Core 7.0 relies on SqlClient 5.0.1, which has a catastrophic memory leak if you make repeated calls within a
BackgroundService. See https://github.com/dotnet/SqlClient/issues/1810One of my wish-list goals is to write a “real-world” sample GraphQL project that uses a Entity Framework back-end and utilizes data loaders and so on, including a React SPA demonstration application with JWT authentication and subscription support. So many of the samples available now can’t really be used for production applications, and/or don’t explain how to implement “real-world” tasks. You can build GraphQL.NET applications either by “code-first”, “schema-first”, or “type-first”, and having the same program implemented each way would allow people to compare writing styles to see what works best for them, and/or compare logic across writing styles.
Just FYI, my own GraphQL project that uses Entity Framework currently is a .NET 6 project, running with GraphQL.NET 7, and EF Core 3.1. The database project is currently also referenced by code that executes on .NET 4.8, so I am prevented from upgrading to EF 5.x or newer. Another of my projects uses the linq2db database layer rather than EF Core.
Transient DbContexts can provide a benefit where if you also have
IApiRepositorya transient, and then reconfigure the field resolvers as previously described (soIApiRepositoryis pulled from the DI scope of the HTTP context), then you can use parallel execution as each field resolver will receive its own instance ofIApiRepositoryand theDbContext. This doesn’t work for my code because I have many services that operate on the DbContext, and it is essential that each DbContext is the same reference so thatSaveChangesworks properly.A transient lifecycle for a DbContext can help in certain situations. In your case, it won’t, at least not yet. The thing is that since
IApiRepositoryis a singleton, the DbContext will also be effectively a singleton. If you registerIApiRepositoryas a transient, then it too will have a singleton effective lifetime because it is created with the graph type that references it.Now if you’re thinking that you can just use a transient (or scoped) schema, that’s true, and in fact GraphQL.NET supports scoped schemas. But it takes a long time to construct a schema, and so it is not recommended for performance reasons.
If you did want to use a scoped schema, remove all your manual registrations as described above, adding the call to
.AddGraphTypes(). Then change the.AddSchema<ApiSchema>()call to specify that it should be created as scoped.There is a danger to transient DbContexts in that transient services are not disposed until the container that holds them is disposed. This is not an issue if the DbContext is created within a scope, but if it is repeatedly created from the root (e.g. from a singleton), then you will have a memory leak. See link here:
https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#disposable-transient-services-captured-by-container
For any other configurations to the execution options, just call
.ConfigureExecutionOptions(opts => { ... });I’d check to be sure that matching versions of Microsoft.EntityFrameworkCore nuget packages are installed. Perhaps you have an older version of Microsoft.EntityFrameworkCore.SqlServer installed than the Microsoft.EntityFrameworkCore package.
I see you have this code in your sample:
This isn’t going to work long-term because Entity Framework does not support concurrent requests, and GraphQL.NET Server is going to execute each request it receives as soon as it arrives, which may be concurrent with another request. You do have queries configured to execute serially, so at least it should work for testing.
Long term you’ll want to have your
DbContextandIApiRepositoryas scoped services, and change your field resolver like so:I would also like to confirm that
IApiRepositoryand theAppDbContextis uses are registered as a singletons in your program? (Note: Entity Framework contexts are scoped services.)I think perhaps you’re misunderstanding what a data loader’s function is. Typically a data loader is used to group calls to the database for a set of ids. For instance, if you had a list of products, and each Product had a ManufacturerId, rather than pulling each manufacturer from the database individually, you can use a data loader to retrieve all the manufacturers at once. However, the root query does not typically need a data loader - it is already a single call that is not expected to be called again in the query.
Take the below sample:
In the sample above, Address uses the
GetOrAddBatchLoadermethod, as it is retrieving a single item per child id, and MenuItem uses theGetOrAddCollectionBatchLoadermethod, as it retrieves a list of items per parent id.You can read more about data loaders here:
Regardless, I don’t immediately see anything wrong with the code sample you provided. Although perhaps not very useful, there is no reason why a SimpleDataLoader cannot be created to load the list of restaurants. I suggest turning on exception stack traces within responses to better diagnose the issue. If you can post the stack trace, I can take another look.
It points to coordinates into input GraphQL query string.
Thanks for the repro 👍 but we need an example query to pass it into your GraphQL server.