-
-
Notifications
You must be signed in to change notification settings - Fork 916
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DataLoader & scoped dependencies concurrent access with parallel execution strategy #3680
Comments
Now I'm actually not so sure about asynchronous dataloader resolution due to this comment: graphql-dotnet/src/GraphQL.DataLoader/DataLoaderBase.cs Lines 119 to 126 in 1710c18
|
If you configure the schema to use serial execution, none of the data loaders will run in parallel with any other code (or each other). See this link: services.AddGraphQL(b => b
.AddExecutionStrategy<SerialExecutionStrategy>(GraphQLParser.AST.OperationType.Query) This is the simplest answer, and you won't have any issues with EF, either within any field resolver or within any data loader. I recommend this if your project uses Entity Framework. If you want to continue using a parallel execution strategy, then as you have experienced, support for service scope is a bit of a manual process. The data loader is going to run outside of the field resolver context, so Note that there is no guarantee that the |
Feel free to provide a PR to update our documentation. All of the docs' source files are here: https://github.com/graphql-dotnet/graphql-dotnet/tree/master/docs2/site/docs |
Provided for by
No existing mechanism. The execution strategy is not aware of the fact that the resolver has created a service scope (not even when If it were me, and I wanted parallel execution with scoped data loaders, I'd make a wrapper class like this: class ScopedBatchDataLoader<TFetcher, TKey, T> : BatchDataLoader<TKey, T>
where TFetcher : IFetcher<TKey, T>
{ /* ... */ }
interface IFetcher<TKey, T>
{
Task<IDictionary<TKey, T>> FetchAsync(IEnumerable<TKey> keys, CancellationToken token);
} and the fetch delegate would do this: await using var scope = serviceProvider.CreateAsyncScope();
var service = scope.GetRequiredService<TFetcher>();
return await service.FetchAsync(keys, token); Then I'd register data loader instances as singletons (being sure to have caching turned off), and since they are now singletons, they can be injected directly into the graph type if desired. I wouldn't use the |
With a parallel execution strategy, this analysis is correct. Parallel execution strategy assumes that you are only using singletons or are managing service scopes as-needed, including within data loader fetch delegates. |
A big no-no - what's the point then? :)
What do you mean by ' Field<ListGraphType<IntGraphType>>("ints_batchkey")
.Argument<string>("id")
.Argument<string>("batchKey")
.Resolve(context =>
{
var loader = accessor.Context.GetOrAddCollectionBatchLoader<string, int>(
context.GetArgument<string>("batchKey"),
async keys =>
{
using var scope = context.RequestServices.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<ScopedDependency>();
return await service.GetByIdBatchedAsync(keys);
});
return loader.LoadAsync(context.GetArgument<string>("id"));
}); The only problem I see is that this code relies on DI infrastructure. Solution with singleton dataloader instance which fetches data using scoped service(s) defined at the DI level is a lot better, thanks! I have a question though, why can't |
Description
DataLoader does not work as expected with scoped dependencies. Documentation provides rather misleading example when scoped dependency can be accessed from the
DataLoader
, i.e. database access with Entity Framework.Current server implementation can guarantee single thread access if and only if:
loaderKey
is set, i.e. only one dataloader exists for such accessmaxBatchSize
property is less or equal to dataloader 'load requests', i.e. only one dataloader exists for such access.Resolve().WithScope()
can't help here as their scope is created for entire node execution time, problem is thatIDataLoaderResult
are resolved after regular node execution -> scope is destroyed ->ObjectDisposedException
or any other problems with dependencies from disposed scope.We also can't use 'root' scope for entire HTTP call,
IDataLoaderResult
are resolved asynchronously, this call is not awaited at the execution strategy level -> concurrent access exception from Entity FrameworkDbContext
, for example.The more of a hack than a solution that comes to mind is manually creating scope before the actual resolver execution and disposing it in the dataloader
Then
chained call. Something like this:Simple schema for reproductino
Steps to reproduce
Simple schema for reproduction
Expected result
No concurrent access to a single scoped dependency - serial
IDataLoaderResult
resolution.OR
Separate scope dependency copy for each dataloader instance - a hook to 'dispose' resolvers which create scope or return
IDataLoaderResult
.Actual result
Concurrent scoped dependency access from multiple threads due to asynchronous
IDataLoader
value resolutionThe text was updated successfully, but these errors were encountered: