1

I'm using HotChocolate with Entity Framework. For some fields, we want to filter One-to-Many collections.

Example :

public class Many
{
    public int Id { get; set; }
    public string? Name {get; set; }
}

public class User
{
    public int Id { get; set; }
    public List<Many> Manies { get; set; }
}

public class UserType : ObjectType<User>
{
    protected override void Configure(IObjectTypeDescriptor<User> descriptor)
    {
        descriptor.BindFieldsExplicitly();
        descriptor.Field(u => u.Id);
        descriptor.Field("manies").Resolve(c => c.Parent<User>().Manies.Where(m => m.Name.StartsWith("A")).ToList());
    }
}

My first question is : Are resolver executed in parallel as stated by HotChocolate documentation for v10 ?

Also I have an issue when I use async/await in Resolver. It seems that in that case (and only in that case) the resolver is launched on another thread (and I'm having issue with DbContext parallel execution).

Example :

descriptor.Field("manies").Resolve(async c => {
    var parent = c.Parent<User>();
    return await GetFilterManiesAsync(parent);
});

But when I force sync, no parallel issue :

descriptor.Field("manies").Resolve(c => {
    var parent = c.Parent<User>();
    return GetFilterManiesAsync(parent).Result;
});

So I'm trying to understand, is this a bug in HotChocolate ? Or is the parallel pipeline only launched on async/await resolver.

P.S. Using HotChocolate v11.0.9

Thanks,

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Yann
  • 1,388
  • 2
  • 11
  • 9
  • 2
    I had a similar issue in the past where Hotchocolate v10 couldn't await in Resolver and the only solution I found was like you stated, force sync. This issue isn't new though, I already asked for a solution in Hotchocolates Slack and nobody seemed to know what the solution might be. – Wilson Silva Apr 02 '21 at 05:37

2 Answers2

1

I'd suggest you go through their official graphql-workshop which talks about this and much other stuff. One of the lectures in there contains the following paragraph:

The GraphQL execution engine will always try to execute fields in parallel in order to optimize data-fetching and reduce wait time. Entity Framework will have a problem with that since a DBContext is not thread-safe.

The way to resolve that is to use DbContext pooling. Using DBContext pooling allows you to issue a DBContext instance for each field needing one. But instead of creating a DBContext instance for every field and throwing it away after using it, you are renting so fields and requests can reuse it.

To set that up, you have to change the registration of your DbContext from the services.AddDbContext<YourContext>(options => ...); to services.AddPooledDbContextFactory<YourContext>(options => ...);. You'll then need to register YourContext as a scoped service that is being resolved from the factory itself each time it's being accessed from some part of the code. That way you'll allow the library itself to do its thing and resolve the fields in parallel.

I'm not exactly sure what happens when you call the Result and force the sync execution, but I guess you somehow managed to block the entire parallel field resolution by doing so.

msmolcic
  • 6,407
  • 8
  • 32
  • 56
  • This will not work as I'm using LazyLoading proxy, and the DbContext used is the one used when the Parent (User) was created. – Yann Apr 01 '21 at 08:48
  • @Yann Why don't you create a repro of your issue on the GitHub then and provide us a link? I'm pretty sure you're just doing something wrong based on the info you gave us in your question description. – msmolcic Apr 01 '21 at 10:30
  • Thanks, here is the issue : https://github.com/ChilliCream/hotchocolate/issues/3411 and the repro case https://github.com/whyseco/ReproHotChocolateIssueAsyncEF – Yann Apr 01 '21 at 14:25
0

Maybe you want to use ServiceKind.Synchronized on your registered service, in order to always execute DbContext synchronous.

mageakos
  • 1
  • 1