graphql-dotnet / relay

A toolset for creating Relay.js compatible GraphQL servers in dotnet.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

GetById() requiring context

PaulKlein opened this issue · comments

Is it possible to access the graphql context in GetById()?
In my case I have some user context in ResolveFieldContext<>.UserContext which I need in order to load by ID.

If it's not currently available, could it be possible to change IRelayNode to include this context somehow?

From some quick research I don't see a way to access the context from the GetById() method nor from the NodeGraphType object. I could draft up a PR to pass the context to the GetById method.

However I'm not sure a PR is possible without it being a "breaking change" as it will be modifying the parameters the GetById method. We could make the context "optional" to prevent breaking changes, but that's somewhat weird as the context will never truly be null. Thoughts?

It would be a breaking change either way, because you won't be able to override a method with an optional parameter unless the overriding member also has the optional parameter. A new member could be added that calls the old deprecated member, but I think it is fine to have breaking changes. The project hasn't been touched in almost 3 years. If anyone is still using this project, they are still using GraphQL.NET 2.4.0, and there are many breaking changes between it and GraphQL.NET 4.x.

I have not looked in depth, but I think it makes sense for GetById to have the context. It would be necessary to have the context if the implementation needs to access context.RequestServices for example. If you're interested in writing a PR, I can review it.

Thanks for taking a look at this. I did end up upgrading to GraphQL.Net 3.x a while back and therefore had to move away from using this library, so this issue isn't a problem for me any more.

Previously I was able to work around the issue by using an approach similar to the data loader context accessor implementation in GraphQL.Net 2.x. - something like the DataLoaderDocumentListener

In my new code I also changed to return an IDataLoaderResult to allow the Ids to be batched and loaded asynchronously, but I see that this is in a separate package in GraphQL.Net 4.x so this might not be practical in this library.

I'm happy to either close or leave open this issue depending on what you guys want to do.

Cheers

Thanks for the update @PaulKlein !

Just FYI, although all of the data loader classes have been moved into the other package for 4.0, the IDataLoaderResult interface alone has remained in the main package to allow the execution strategy to identify and process data loader nodes. The IDataLoaderResult interface can also be used for any type of node that needs to be processed after all other pending nodes have been resolved. It sounds as though this might be the case for you.

Out of curiosity might you describe what parts of this library were useful to you, and what might you still be using if the project had been maintained properly? Thanks!

Thanks @Shane32 , good to know about that IDataLoaderResult. We're in the process of migrating to 4.x at the moment actually but it won't be a problem for us to just reference that separate data loader package as we do make heavy use of the batch loaders.

I think one very useful part of this library is the helpers around dealing with relay connections. Many of our connections are fairly static lists, so using the offset-based cursors was working well using the helpers.

We were not using mutations when we were originally using this library, but are now starting to look at them, so that is another thing that would be useful. I didn't actually realise relay mutations imposed extra constraints on mutations so another benefit of the project is to consolidate all the requirements to be 'relay compatible' into one nice place.

One thing we're trying to figure out at the moment is using cursors based on record ids instead of offset-based cursors. Some of our connections are e.g. things like logs, where they want to get a consistent paged stream of events without missing/duplicate records across pages. This is where offset-based paging struggles when new records are being added all the time. To make this more complicated we allow the user to specify custom sorting and filtering on the connection, so we can't just use where id > x.

We're using Entity Framework under the hood so I think we might be stuck here until the EF team implement something like dotnet/efcore#14104. I don't know if there are any generic helpers/classes that could help with id-based cursors that would be a good fit for this project - it's possibly all too tied in with the underlying code to be generalised easily.

Finally one other thing that we really struggled with is to combine batch loaders with connections. Again this is probably too specific and tied in with the other packages, but imagine trying to do something like e.g.

query {
  users(first: 100) {
    items {
       name
       friends(first: 50) {
           items {
              name
         }
    }
}

We wanted to avoid loading the friends for each user separately. It ended up being quite a lot of code to batch-load the friend queries in the connection resolvers to fetch this data efficiently.

I'm not sure if there are any generic helpers that could live in this project around these pain points or if it's too intertwined with the underlying resolver implementations but just FYI what kinds of things we're using and what problems we're running into

Thanks again for your help and all the hard work you do on these projects!

No problem and thanks for the feedback!

Just thinking out loud about your nested-connection issue....: Perhaps if you configured friends to combine the arguments and id into a class to be used as a key for a data loader, then you can write a sql query to retrieve all friends where itemIds.Contains(row.ItemId) and use SQL PARTITION BY to partition by ItemId and attach a row number for each record. But then you'd have to append where queries for each ItemId - something like .Where(x => x.ItemId == itemId && x.RowNumber >= after && x.RowNumber < (after + first)). These queries need to be ORed together, and there are helper functions for this available. The hard part will be returning last queries as well, where you don't know the total count. But you could start by grouping all of these types of queries (ones that use last instead of first) separately, so only either 1 or 2 queries execute maximum no matter how many friends are requested.

Note that EF Core doesn't have ROW_NUMBER support included, but it can be added. Check out https://www.thinktecture.com/en/entity-framework-core/row_number-support-in-2-1/

I use linq2db where ROW_NUMBER is part of the supported feature set. Here's a sample of its use: linq2db/linq2db.EntityFrameworkCore#130 (comment) Linq2db can be used on top of Entity Framework if you desire. I think it also has helper functions included for ORing expressions together for use in a Where query.

If you support multiple sort orders on the friends connection then you'd have to generate different queries for each sort order as well. I mean of course you could union all the queries together into one but that doesn't reduce SQL execution time.

So I'd group the data loader key by the aspects that require different queries (such as first vs last, or sort order if applicable) and enumerate over the different sets, executing a different query for each.

As you say, it's a bunch of code, but perhaps not so terribly bad.

Hi @Shane32 , thanks a lot for that extra info and pointers - interesting stuff there for sure. I'll dig through it and see what we can come up with.

I haven't even looked at last / before queries yet - we're only doing forward-paging for starters. Hopefully that will simplify things for us.

Thanks again