I have a data source provider :
public class DSProvider
{
public IQueryable<Product> Products
{
get
{
return _repo.Products.AsQueryable();
}
}
}
The repository in the above example currently gets ALL the records (of Products) from DB and then applies the filters, this just does not sound right if you had 50000 requests/sec from a website.How can you limit the repository to just return required info from DB without converting the service to a tightly coupled request option i.e. opposite of what you try to achieve by using oData?
So to summarize I would like to know if its possible to query the DB on the oData options supplied by the user so that my request does not always have to get all products and then apply filters of oData.
I found out after doing a small POC that Entity framework takes care of building dynamic query based on the request.
Related
Though [UseProjection] middleware works out of the box, the default UsePaging seems to still apply Offset based selection in the backend SQLServer Query. I want to use the [UseProjection] provided by HotChocolate, but want to have customized cursor based pagination.
Initially I planned to use Dapper as the micro-OR/M with my own custom pagination logic implemented in Resolver and my custom SQL Query.
However the problem with the above approach is that I am not able to leverage the advantages provided by the default [UseProjections] of HotChocolate.
So I am fully using HotChocolate with Entity Framework Core.
With this standard approach my Projections work as expected. But now I am not able to apply custom pagination. The back-end SQL Server Query in the Profiler shows "OFFSET #__p_0 ROWS FETCH NEXT #__p_1 ROWS ONLY" which is still an Offset query really.
My Query class:
namespace Efct2.GraphQL.Query
{
[ExtendObjectType(typeof(Query))]
public class StyleQuery
{
[UsePaging]
[UseProjection]
public async Task<IQueryable<Style>> Styles([Service] IStyleService styleService)
{
return await styleService.GetAsync();
}
}
}
The StylService and StyleRepository have an implementation similar to the one given in HotChocolate Projections related question
So I am not attaching the code for those here. However I can add it if required.
My GraphQL input query is as below:
styles (first: 10, after: "OQ==") {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
nodes {
companyCode
divisionCode
itemNumber
colorCode
description
categoryCode
classCode
}
}
}
Given below is the SQLServer SQL from the backend profiler:
exec sp_executesql N'SELECT [s].[Company_Code] AS [CompanyCode], [s].[Division_Code] AS [DivisionCode], [s].[Item_Number] AS [ItemNumber], [s].[Color_Code] AS [ColorCode], [s].[Description], [s].[Category_Code] AS [CategoryCode], [s].[Class_Code] AS [ClassCode]
FROM [Style] AS [s]
ORDER BY (SELECT 1)
OFFSET #__p_0 ROWS FETCH NEXT #__p_1 ROWS ONLY',
N'#__p_0 int,#__p_1 int',#__p_0=10,#__p_1=11
Would really appreciate help on the above.
Yes, In Hot Chocolate the paging is abstracted through the IPagingHandler and the IPagingProvider.
https://github.com/ChilliCream/hotchocolate/blob/main/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingProvider.cs
https://github.com/ChilliCream/hotchocolate/blob/main/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingHandler.cs
I'm the new guy in the GraphQL world and trying to find a way to have multiple Query types or how to split the Query type into multiple files..
I use Hot Chocolate for Asp.Net Core, and everything looks good and works.
But what if I need to combine few queries in one GraphQL API? Some really unrelated stuff, f.e. DogsQuery and CarsQuery.
In Asp.Net, I write similar to:
public void ConfigureServices(IServiceCollection services)
{
services
.AddGraphQLServer()
//.AddQueryType<DogsQuery>()
.AddQueryType<CarsQuery>();
}
It works fine if I use only one Query class simultaneously (Dogs or Cars). But how to use both?
I've searched a lot but can't find the answer.
You cannot have multiple Query/Mutation/Subscription types in graphql, but you can split the types to multiple files in HotChocolate.
You can use the [ExtendObjectType(Name = "Query")] attribute above both your query types. This works for the Subscriptions ([ExtendObjectType(Name = "Subscription")]) and Mutations ([ExtendObjectType(Name = "Mutation")]) the same way.
This attribute is used for merging any two graphql types in the same HotChocolate server. The name value must be the name of the GraphQl type you want to merge your C# class into. In this case it is Query.
After doing that, you can add the types to your server like this:
public void ConfigureServices(IServiceCollection services)
{
services
.AddGraphQLServer()
.AddQueryType(d => d.Name("Query"))
.AddTypeExtension<DogsQuery>()
.AddTypeExtension<CarsQuery>();
}
You can find this and many more useful things in the workshop example on Github:
https://github.com/ChilliCream/graphql-workshop
I haven't messed with OData in a while, but I remember it being really useful. So I've gone for a .NetCore 3.1 EFCore + OData architecture for my API. With a view to make it fully generic etc. etc.
Doing a little test, I can get the expected results from my browser: e.g.
https://localhost:44310/things?someidfield=44
Cool I get back the JSON I was expecting! But it's sooo slow, why? Looking at the SQL (Profiler) I can see it has no WHERE clause, it's getting everything from the database and filtering it in memory, on half a million records?
What am I missing here? I've tried a number of ways to write the GET method. Starting with not passing any queryOptions (which worked! but same result underneath) and then the below where I explicitly apply the options to by EFCore entity.
[HttpGet]
[EnableQuery]
public async Task<IEnumerable<thing>> GetThingsAsync(ODataQueryOptions<thing> queryOptions)
{
return await queryOptions.ApplyTo(DB.thing).Cast<thing>().ToListAsync();
}
The result set is being loaded in memory because you're calling ToListAsync() and returning an IEnumerable.
If your GetThingsAsync method returns an IQueryable<T> (instead of IEnumerable<T>), then the query will be applied to the database and only the filtered data will be fetched.
If DB.thing is an EFCore DbSet (which implements IQueryable<T>), then you can simplify your method as
[HttpGet]
[EnableQuery]
public Task GetThingsAsync()
{
return DB.thing;
}
Furthermore, like some in the comment already mentioned, the correct syntax for filtering in your case would be ?$filter=someidfield eq 44
Before you start reading: I have looked at the GraphQL documentation, but my usecase is so specific and I only need the data once, and therefore I allow myself to ask the community for help on this one to save some time and frustration (not planning to learn GraphQL in the future)
Intro
I am a CS student developing an app for Flutter on the side, where I need information about the name and location of every bus stop in a specific county in Norway. Luckily, there's an open GraphQL API for this (API URL: https://api.entur.io/stop-places/v1/graphql). The thing is, I don't know how to query a GraphQL API, and I do not want to spend time learning it as I am only going to fetch the data once and be done with it.
Here's the IDE for the API: https://api.entur.io/stop-places/v1/ide
And this is the exact query I want to perform as I want to fetch bus stops located in the county of Trondheim:
{
stopPlace(stopPlaceType: onstreetBus, countyReference: "Trondheim") {
name {
value
}
... on StopPlace {
quays {
geometry {
coordinates
}
}
}
}
}
The problem with this query though, is that I don't get any data when passing "Trondheim" to the countyReference (without countyReference I get the data, but not for Trondheim). I've tried using the official municipal number for the county as well without any luck, and the documentation of the API is rather poor... Maybe this is something I'll have to contact the people responsible for the API to figure out, which shouldn't be a problem.
But now back to the real problem - how can I make this query using the GraphQL package for Dart? Here's the package I'm planning to use: (https://pub.dev/packages/graphql)
I want to create a bus stop object for each bus stop, and I want to put them all in a list. Here is my bus stop model:
class BusStop with ChangeNotifier {
final String id;
final String name;
final LatLng location;
BusStop({
this.id,
this.name,
this.location
});
}
When it comes to authentication, here's what the documentation says:
This API is open under NLOD licence, however, it is required that all consumers identify themselves by using the header ET-Client-Name. Entur will deploy strict rate-limiting policies on API-consumers who do not identify with a header and reserves the right to block unidentified consumers. The structure of ET-Client-Name should be: "company - application"
Header examples: "brakar - journeyplanner" "fosen_utvikling - departureboard" "norway_bussekspress - nwy-app"
Link to API documentation: https://developer.entur.org/pages-nsr-nsr
Would be great to know how I should go about this as well! I'm grateful for every answers to this, I know I am being lazy here as of learning GraphQL, but for my usecase I thought it would take less time and frustration by asking here!
Getting the query right
First of all you seem to have GraphQL quite figured out. There isn't really much more to it than what you are doing. What queries an API supports depends on the API. The problem you seem to have is more related to the specific API that you are using. I might have figured the right query out for you and if not I will quickly explain what I did and maybe you can improve the query yourself:
{
stopPlace(stopPlaceType: onstreetBus, municipalityReference: "KVE:TopographicPlace:5001") {
name {
value
}
... on StopPlace {
quays {
geometry {
coordinates
}
}
}
}
}
So to get to this I started finding out more about "Trondheim" bei using the topographicPlace query.
{
topographicPlace(query: "Trondheim") {
id
name {
value
}
topographicPlaceType
parentTopographicPlace {
id
name {
value
}
}
}
}
If you do that you will see that "Trondheim" is not a county according to the API: "topographicPlaceType": "municipality". I have no idea what municipality is but the is a different filter for this type on the query that you provided. Then putting "Trondheim" there didn't yield any results so I tried the ID of Trondheim. This now gives me a bunch of results.
About the GraphQL client that you are using:
This seems to be an "Apollo Client" clone in Dart. Apollo Client is a heavy piece of software that comes with a lot of awesome features when used in a frontend application. You probably just want to make a single GraphQL request from a backend. I would recommend using a simple HTTP client to send a POST request to the GraphQL API and a JSON body (don't forget content type header) with the following properties: query containing the query string from above and variables a JSON object mapping variable names to values (only needed if you decide to add variables to your query.
I doubt this is just specific to NHibernate. But I have code as follows....
public class ClientController : ApiController
{
// GET /api/<controller>
public IQueryable<Api.Client> Get()
{
return Repositories.Clients.Query().Select(c => Mapper.Map<Client, Api.Client>(c));
}
I basically want to Query the database using the Odata criteria.... get the relevant 'Client' objects, and the convert them to the DTO 'Api.Client'.
But... the code as is, doesn't work. Because NHibernate doesn't know what to do the with the Mapper.... It really wants the query to come before the .Select. But I'm not sure I can get the Odata Query first?
It will work if I do
return Repositories.Clients.Query().Select(c => Mapper.Map<Client, Api.Client>(c)).ToList().AsQueryable();
But that's a bit sucky as you have to get ALL the clients from the database to do the OData query on.
Is there anyway to get the "Select" to happen after the OData query? Or another way to approach this?
I did not test it yet but the Open Source project NHibernate.OData could be useful for you.
The problem is that you are trying to execute C# code (Mapper.Map) inside the NH call (that is translated to SQL)
You'd have to map Api.Client manually, or create a Mapper implementation that returns an Expression<Func<Client, Api.Client>> and pass it directly as a parameter to Select().
Even with that, I'm not sure if NHibernate will translate it. But you can try.
AutoMapper supports this scenario with the Queryable Extensions
public IQueryable<Api.Client> Get() {
return Repositories.Clients.Query().Select(c => Mapper.Map<Client, Api.Client>(c));
}
becomes
public IQueryable<Api.Client> Get() {
return Repositories.Clients.Query().ProjectTo<Api.Client>(mapper.ConfigurationProvider);
}