Hotchocolate: Correctly scoping EF context

Created on 10 Sep 2019  ·  10Comments  ·  Source: ChilliCream/hotchocolate

Hi, I was wondering what was the correct way of scoping EF context when for example we try to resolve multiple fields/queries which actually use context each on their own? For example lets say that this is actual GraphQL query you want to resolve:

{
  blogs{
    name
  }
  users{
    email
  }
}

For that for example I have this field/resolver definition in HotChocholate (Note that DatabaseContext is actually EF DbContext implementation) :

public class RootQuery
{
        public IQueryable<User> Users([Service]DatabaseContext db)
        {
            return db.Set<User>()
                .AsNoTracking();
        }

        public IQueryable<Blog> Blogs([Service]DatabaseContext db)
        {
            return db.Set<Blog>()
                .AsNoTracking();
        }
}

public class RootQueryObject : ObjectType<RootQuery>
{
        protected override void Configure(IObjectTypeDescriptor<RootQuery> descriptor)
        {
            descriptor.Field(x => x.Users(default(DatabaseContext)));
            descriptor.Field(x => x.Blogs(default(DatabaseContext)))
        }
}

Now this by default results in next error:
A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations.

If I'm getting this right [Service] will resolve actual instance but reuse in the scope of multiple field resolvers. This can be easily avoided by registering DbContext as transient dependency.
Another question that comes to my mind is Can you somehow plug into the HotChocolate to actually create new service scope before each Field execution? That would effectively remove the issue.

One thing that came to my mind was to actually create the scope in the middleware and resolve actual context manually in the field/resolver from the scope that would be passed via middleware context etc.

Also, thanks for a great project 😄

❓ question 🌶 hot chocolate

Most helpful comment

Version 10.4 will now provide native support for this. So stay tuned for the release next week...

All 10 comments

I will answer you tomorrow...

Can you somehow plug into the HotChocolate to actually create new service scope before each Field execution? That would effectively remove the issue.

This would be quite expensive and slow things down considerably.

I am still thinking on how to really solve this thing. We want to be able to resolve in parallel but we also might want to pool the contexts so that we do not get to many and work efficiently with our resources.

For the moment transient is the best you can do here. We are working on something called query execution plans which will address things like that.

If I'm getting this right [Service] will resolve actual instance but reuse in the scope of multiple field resolvers.

ASP.net core passes a request service provider that is scoped per request. [Service] defines that the argument has to be resolved by using the request service provider.

One thing that came to my mind was to actually create the scope in the middleware and resolve actual context manually in the field/resolver from the scope that would be passed via middleware context etc.

You could do this but it might actually be better to resolve the context as service like you do now and then get rid of it with the middleware. The middleware would have to be the first middleware in the pipeline.

Also I think all of this not as it should be. The whole thing should be easier and we will put a lot of effort in making this effortless with V11.

Awesome, thanks for a quick reponse. I'll keep track of the releases then (until then Transient it is) 😄.

677

@DenisPav I encountered a similar problem. In my case, it was more appropriate to implement sequential processing of fields. I solved this problem by using SemaphoreSlim and custom middleware.

Hi, I have posted another kind of solution in this issue

Version 10.4 will now provide native support for this. So stay tuned for the release next week...

Quick note: I'm currently required to work with v11 preview which is missing the ForceSerialExecution option. Setting the service lifetime to Transient doesn't work when your query fetches from multiple DbSets. For example: query Test{SomeStuff{Id} SomeMoreStuff{Name}} will still fail, albeit intermittently depending on timing. This blog post says there's DbContext pooling on it's way but I haven't been able to find anything. Maybe it's not out yet? In the meantime I'm constructing a new DbContext for each relevant field.

descriptor.Field("Test")
.Description("Test info")
.Type<ListType<TestType>>()
.UseSelection()
.UseFiltering()
.UseSorting()
.Authorize("AdminPolicy")
.Resolver(() => new DatabaseContext().Test); //< This here

descriptor.Field("SomeMoreStuff")
.Description("Some more stuff")
.Type<ListType<SomeMoreStuffType>>()
.UseSelection()
.UseFiltering()
.UseSorting()
.Authorize("AdminPolicy")
.Resolver(() => new DatabaseContext().SomeMoreStuff); //< This here
Was this page helpful?
0 / 5 - 0 ratings