Hot Chocolate how to query nested tree - c#

I've set up a project using HotChocolate and Entity Framework. The query looks like this.
post -> comment -> comment...
fragment comment on CommentType {
id
creatorId
text
timeStamp
}
query {
posts {
edges {
node {
id
communityName
title
text
timestamp
creatorId
comments {
postId
...comment
replies {
...comment
replies {
...comment
replies {
...comment
}
}
}
}
}
}
}
}
The Commenttype looks like this. I use dataLoaders for the nested relationships.
public class CommentType : ISearchResult {
[GraphQLNonNullType]
public Guid Id { get; set; }
public string Text { get; set; }
public DateTime TimeStamp { get; set; }
[IsProjected(true)]
public string CreatorId { get; set; }
public async Task<UserType> User([DataLoader] UserDataLoader dataLoader) {
return await dataLoader.LoadAsync(CreatorId, CancellationToken.None);
}
[IsProjected(true)]
public Guid PostId { get; set; }
public async Task<PostType> Post([DataLoader] PostDataLoader dataLoader) {
return await dataLoader.LoadAsync(PostId, CancellationToken.None);
}
[IsProjected(true)]
public Guid? ReplyToId { get; set; }
public async Task<CommentType?> ReplyTo([DataLoader] CommentDataLoader dataLoader) {
if (ReplyToId == null)
return null;
else
return (CommentType?)await dataLoader.LoadAsync(ReplyToId, CancellationToken.None);
}
[IsProjected(true)]
public IEnumerable<Guid> ReplyIds { get; set; }
[UseProjection]
public async Task<IEnumerable<CommentType>> Replies([DataLoader] CommentDataLoader dataLoader) {
return await dataLoader.LoadAsync(ReplyIds.ToArray(), CancellationToken.None);
}
}
`
[ExtendObjectType(typeof(Query))]
public class PostQuery {
private readonly IMapper _mapper;
public PostQuery(IMapper mapper) {
_mapper = mapper;
}
[UseDbContext(typeof(ApplicationDbContext))]
[UsePaging(IncludeTotalCount = true, DefaultPageSize = 20)]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<PostType> GetPosts([ScopedService] ApplicationDbContext context) {
return context.Posts.ProjectTo<PostType>(_mapper.ConfigurationProvider);
}
}
This works but it never goes deeper than the first nested object in any of my queries. I've tried adding projection to the nested list, but that doesn't seem to help either.
{
"data": {
"posts": {
"edges": [
{
"node": {
"id": "e9f2cedc-6c1d-4977-81b7-65b819e8131d",
"communityName": "Test Community",
"title": "Test Post",
"text": "testing post",
"timestamp": "2022-12-23T16:40:14.649Z",
"creatorId": "ilIlLJ1g7CTRhcCfZAWG5V9j4rx1",
"comments": [
{
"postId": "e9f2cedc-6c1d-4977-81b7-65b819e8131d",
"id": "7804eada-64ea-4a03-b9aa-7127affb9f32",
"creatorId": "ilIlLJ1g7CTRhcCfZAWG5V9j4rx1",
"text": "comment on post",
"timeStamp": "2022-12-23T17:51:33.808Z",
"replies": []
}
]
}
}
]
}
}
}
Is there a specific way to do this with HotChocolate?
Edit: Ok so I forgot to add the Include(c => c.Replies) when fetching the ids from the repository, which makes sense since the dataloader doesn't make use of IQueryable. I suppose won't overfetch since it only reaches this point if it's included in the query anyway. This seems to have solved it.

Related

How to use class object instead of using JObject in .NET Core

I want to return C# class object instead of using JObject in here. Could someone can tell me how to use it.
private async Task<JObject> GetReceiptById(string Id, string name)
{
var response = await _ApiService.Get(Id, name);
var responseStr = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
return JObject.Parse(responseStr);
}
throw new Exception(responseStr);
}
this method is return (return JObject.Parse(responseStr)); below JSON output. for that how to create a new class. I am not sure how to apply all in one class.
{
"receipts": [
{
"ReceiptHeader": {
"Company": "DHSC",
"ErpOrderNum": "730",
"DateTimeStamp": "2022-05-14T13:43:57.017"
},
"ReceiptDetail": [
{
"Line": 1.0,
"ITEM": "PP1016",
"ITEM_NET_PRICE": 0.0
},
{
"Line": 2.0,
"ITEM": "PP1016",
"ITEM_NET_PRICE": 0.0
}
],
"XrefItemsMapping": [],
"ReceiptContainer": [],
"ReceiptChildContainer": [],
"rPrefDO": {
"Active": null,
"AllowLocationOverride": null,
"DateTimeStamp": null
}
}
]
}
You can bind the Response Content to a known Type using ReadAsAsync<T>().
https://learn.microsoft.com/en-us/previous-versions/aspnet/hh835763(v=vs.118)
var result = await response.Content.ReadAsAsync<T>();
In your example you will also run into further issues as you are not closing your response after getting it from the Api Service Get method.
Below is a possible solution where you send your object type to the Get method. (not tested)
public virtual async Task<T> GetApiCall<T>(Id, name)
{
//create HttpClient to access the API
var httpClient = NewHttpClient();
//clear accept headers
httpClient.DefaultRequestHeaders.Accept.Clear();
//add accept json
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//return the client for use
using (var client = await httpClient )
{
//create the response
HttpResponseMessage response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
//create return object
try
{
var result = await response.Content.ReadAsAsync<T>();
//dispose of the response
response.Dispose();
return result;
}
catch
{
throw;
}
}
// do something here when the response fails for example
var error = await response.Content.ReadAsStringAsync();
//dispose of the response
response.Dispose();
throw new Exception(error);
}
}
What you probably looking for is Deserialization
you can achieve it with
var model = JsonConvert.Deserialize<YourClass>(responseStr);
return model;
but the class (YourClass) properties must match the json string you provided in responseStr.
As the comments section you asked for a generated class:
here is what should look like, after you generate the class.
Note: most of the times, you will need to edit the generated class.
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class Receipt
{
public ReceiptHeader ReceiptHeader { get; set; }
public List<ReceiptDetail> ReceiptDetail { get; set; }
public List<object> XrefItemsMapping { get; set; }
public List<object> ReceiptContainer { get; set; }
public List<object> ReceiptChildContainer { get; set; }
public RPrefDO rPrefDO { get; set; }
}
public class ReceiptDetail
{
public double Line { get; set; }
public string ITEM { get; set; }
public double ITEM_NET_PRICE { get; set; }
}
public class ReceiptHeader
{
public string Company { get; set; }
public string ErpOrderNum { get; set; }
public DateTime DateTimeStamp { get; set; }
}
public class Root
{
public List<Receipt> receipts { get; set; }
}
public class RPrefDO
{
public object Active { get; set; }
public object AllowLocationOverride { get; set; }
public object DateTimeStamp { get; set; }
}
generated by: https://json2csharp.com/

Azure Function HTTP trigger to receive nested JSON and store in Cosmos DB

Azure Function and C# virgin here. Forgive my ignorance.
I am attempting to create an Azure Function HTTP trigger that will take the received nested JSON data and store into Cosmos DB using POST. I have created a function that will store JSON that has nothing nested, but can not understand how to handle the nested portion.
Code Sample(run.csx):
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static IActionResult Run(HttpRequest req, out object testDocument, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
dynamic body = await req.Content.ReadAsStringAsync();
var e = JsonConvert.DeserializeObject < Root > (body as string);
return req.CreateResponse(HttpStatusCode.OK, e);
}
public class Vehicles {
public string car {
get;
set;
}
public string bike {
get;
set;
}
public string plane {
get;
set;
}
}
public class Root {
public string name {
get;
set;
}
public int age {
get;
set;
}
public Vehicles vehicles {
get;
set;
}
}
}
if (!string.IsNullOrEmpty(Category) && !string.IsNullOrEmpty(Label)) {
testDocument = new {
name,
age,
vehicles,
car,
bike,
plane
}
return (ActionResult) new OkResult();
} else {
testDocument = null;
return (ActionResult) new BadRequestResult();
}
}
Bindings(function.json):
{
"bindings": [
{
"authLevel": "anonymous",
"name": "req",
"type": "httpTrigger",
"direction": "in",
"methods": [
"get",
"post"
]
},
{
"name": "$return",
"type": "http",
"direction": "out"
},
{
"name": "testDocument",
"direction": "out",
"type": "cosmosDB",
"connectionStringSetting": "XXXX-test_DOCUMENTDB",
"databaseName": "testDatabase",
"collectionName": "testCollection",
"createIfNotExists": true
}
]
}
Input:
{
"name":"Ram",
"age":27,
"vehicles": {
"car":"limousine",
"bike":"ktm-duke",
"plane":"lufthansa"
}
}
This code produces 500 Internal Server Error when run.
If anyone could point out where the errors are or if it is even structured correctly, I would appreciate it greatly.
Thank you in advance.
The error is because your class structure is not complete. It looks like you have cut'n'pasted from two different code files. This has nothing to do with the fact that the Root class has a nested class property.
It helps to create a C# function project that you can debug with locally if you want to persist with doing this purely in the browser.
If you comment out the erroneous code, it should compile:
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static IActionResult Run(HttpRequest req, out object testDocument, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
dynamic body = await req.Content.ReadAsStringAsync();
var e = JsonConvert.DeserializeObject<Root>(body as string);
return req.CreateResponse(HttpStatusCode.OK, e);
}
public class Vehicles
{
public string car
{
get;
set;
}
public string bike
{
get;
set;
}
public string plane
{
get;
set;
}
}
public class Root
{
public string name
{
get;
set;
}
public int age
{
get;
set;
}
public Vehicles vehicles
{
get;
set;
}
}
// }
// if (!string.IsNullOrEmpty(Category) && !string.IsNullOrEmpty(Label))
// {
// testDocument = new
// {
// name,
// age,
// vehicles,
// car,
// bike,
// plane
// }
//
//
// return (ActionResult)new OkResult();
// }
// else
// {
// testDocument = null;
// return (ActionResult)new BadRequestResult();
// }
//}
However you will see this doesn't do anything with your CosmosDB logic.
Also, please DO NOT abuse dynamic the way you have there, the response from ReadAsStringAsync is a Task<string>, just use it:
string body = await req.Content.ReadAsStringAsync();
var e = JsonConvert.DeserializeObject<Root>(body);
return req.CreateResponse(HttpStatusCode.OK, e);

Converting JToken into a complex Object using ToObject

sorry for my bad english. But i need a little help with this method (ToObject)
I have this class
namespace Proyects
{
public class AProductType
{
public DateTime CreationDate { get; set; }
public string Product { get; set; }
}
public class AProduct
{
public AProductType A_ProductType { get; set; }
}
public class AProductPlantType
{
public string SerialNumberProfile { get; set; }
}
public class ToPlant
{
public List<AProductPlantType> A_ProductPlantType { get; set; }
}
public class AProductDescriptionType
{
public string Language { get; set; }
public string Product { get; set; }
public string ProductDescription { get; set; }
}
public class ToDescription
{
public List<AProductDescriptionType> A_ProductDescriptionType { get; set; }
}
public class Root
{
public AProduct A_Product { get; set; }
public ToPlant to_Plant { get; set; }
public ToDescription to_Description { get; set; }
}
}
I am currently using the Root one to save a Jtoken in there. But i cant save the data on the propieties from Root class.
For example:
var object= myJson.ToObject<Root>();
If i try to save data on the propiety Product from AProductType, i cant access there using using ToOject. I try someting like this
var object= myJson.ToObject<Root>().A_Product.A_ProductType.Product;
And dont work, object var become null. I need some way to save the data around this complex object saving then in the propietes from Root.
Really sorry for my english, Thanks!!!
Edit: Json file
{
"A_Product": {
"A_ProductType": {
"CreationDate": "2020-01-17T00:00:00",
"Product": "158"
}
},
"to_Plant": {
"A_ProductPlantType": [
{
"SerialNumberProfile": "E001"
}
]
},
"to_Description": {
"A_ProductDescriptionType": [
{
"Language": "EN",
"Product": "158",
"ProductDescription": "Terminal LaP (nro de serie + equipo)"
},
{
"Language": "ES",
"Product": "158",
"ProductDescription": "Terminal LaP"
}
]
}
}
Edit 2:
private static List<string> retrieveData(JObject ob, List<Root> listaObjetos)
{
List<string> ListaCodigoProducto = new List<string>();
Root objetoRot = new Root();
var A_Product = ob["A_Product"];
if (A_Product.HasValues)
{
var validacion = ob["A_Product"]["A_ProductType"];
if (validacion.Type == JTokenType.Object)
{
var objeto = validacion.ToObject<AProductType>();
ListaCodigoProducto.Add(objeto.Product);
objetoRot.A_Product.A_ProductType.Product = objeto.Product;
listaObjetos.Add(objetoRot);
}
When i try to save the product number on
objetoRot.A_Product.A_ProductType.Product
It shows NullReference exception, i cant access to the propiety in the Root object
The deserializing code is working just fine. Your problem is that you're accessing objects that aren't there.
When you say Root objetoRot = new Root();, you are creating a new, empty Root. Its A_Product value will be null. A few lines down, you are saying objetoRot.A_Product.A_ProductType.Product = objeto.Product;. But you can't get objetoRot.A_Product.A_ProductType because there is no objetoRot.A_Product to access properties on.

Returning List with Children in ASP.NET CORE with firebase

I am trying to do a get all with an ASP.NET Core project that uses this firebase library and I can't seem to return the children nested in an object. I have 3 classes: Route, Via & Waypoints(Serves as a bridge for JSON Deserialization).
public class Route
{
public string Route_ID { get; set; }
public string Destination { get; set; }
public string Origin { get; set; }
public Waypoints Stops { get; set; }
public Route()
{
}
}
public class Via
{
public string Via_ID { get; set; }
public string Route_ID { get; set; }
public int Seq_Number { get; set; }
public string Coordonnees { get; set; }
public string Description { get; set; }
public Via()
{
}
}
public class Waypoints
{
public List<Via> Vias;
public Waypoints()
{
}
}
In my GET method I go Fetch everything from my Routes and want to return it as one JSON List containing all my routes along with their waypoints but it only returns an empty list of Waypoints:
[HttpGet]
public async Task<IEnumerable<Route>> Get()
{
List<Route> routes = (await firebaseClient
.Child("routes")
.OrderByKey()
.OnceAsync<Route>())
.Select(item =>
new Route
{
Route_ID = item.Key,
Origin = item.Object.Origin,
Destination = item.Object.Destination,
Waypoints = item.Object.Waypoints
}).ToList();
foreach (Route route in routes)
{
List<Via> vias = (await firebaseClient
.Child("routes")
.Child(route.Route_ID)
.Child("Waypoints")
.OrderByKey()
.OnceAsync<Via>())
.Select(waypoint =>
new Via
{
Via_ID = waypoint.Key,
Route_ID = waypoint.Object.Route_ID,
Coordonnees = waypoint.Object.Coordonnees,
Seq_Number = waypoint.Object.Seq_Number,
Description = waypoint.Object.Description
}).ToList();
if(vias.Count > 0)
{
route.Stops.Vias = vias;
}
}
return routes;
}
My data structure:
{
"routes" : {
"987321": {
"Destination": "13.13;-12.34",
"Origin": "12.12;-12.12",
"Route_ID": "987321",
"Waypoints": {
"4d5e6f": {
"coordonnees": "45.8;-74.7",
"description": "Description",
"route_id": "987321",
"seq_number": 2,
"via_id": "4d5e6f"
},
"111222": {
"coordonnees": "45.8;-74.7",
"description": "Description",
"route_id": "987321",
"seq_number": 1,
"via_id": "111222"
}
}
}
}
}
And finally my call result:
[
{
"route_ID": "987321",
"destination": "13.13;-12.34",
"origin": "12.12;-12.12",
"waypoints": {}
}
]
It seems the Deserializing doesn't go further than the first layer of children. Is there any solution to this?
Thanks to Rena's suggestion, I figured out that the problem was located in my Waypoints bridging class that was missing a { get; set; }
Here is the change that was made to my class:
public class Waypoints
{
public List<Via> Vias { get; set; }
public Waypoints()
{
}
}

IOptions not getting the values from appsettings.Development.json

I am trying to create a gateway api using net core. When I try to redirect the call using app.route :
app.Run(async (context) =>
{
using (var serviceScope = app.ApplicationServices.CreateScope())
{
var routing = serviceScope.ServiceProvider.GetService<IRoutingService>();
var content = await routing.RouteRequest(context.Request);
await context.Response.WriteAsync(await content.Content.ReadAsStringAsync());
content.Dispose();
// Seed the database.
}
});
... And RoutingService service starts like :
public class RoutingService : IRoutingService
{
private readonly RouteManagement _routeManagement;
static HttpClient _client = new HttpClient();
public RoutingService(IOptions<RouteManagement> routeManagement)
{
_routeManagement = routeManagement.Value;
}
...
.. I can not get the values from json file filled. The following is the json file :
{
"tokenManagement": {
"secret": "Any String used to sign and verify JWT Tokens, Replace this string with your own Secret",
"issuer": "threenine.co.uk",
"audience": "SampleAudience",
"accessExpiration": 30,
"refreshExpiration": 60
},
"routeManagement": {
"Routes": [
{
"Endpoint": "/coupons",
"Destination": {
"Uri": "http://localhost:30561/coupons/",
"RequiresAuthentication": "true"
}
},
{
"Endpoint": "/songs",
"Destination": {
"Uri": "http://localhost:8091/songs/",
"RequiresAuthentication": "false"
}
}
]
}
}
Am I doing smth wrong? The following is the class RouteManagement
public class RouteManagement
{
public List<Routes> Routes { get; set; }
}
public class Routes
{
public string Endpoint { get; set; }
public Routes.DestinationManagement Destination { get; set; }
public class DestinationManagement
{
private DestinationManagement()
{
Uri = "/";
RequiresAuthentication = false;
}
public string Uri { get; set; }
public bool RequiresAuthentication { get; set; }
}
}
Follow steps below to resolve your issue:
Register RouteManagement
services.Configure<RouteManagement>(Configuration.GetSection("routeManagement"));
You need to make DestinationManagement() public, otherwise, it will fail to initialize the DestinationManagement
public class RouteManagement
{
public List<Routes> Routes { get; set; }
}
public class Routes
{
public string Endpoint { get; set; }
public Routes.DestinationManagement Destination { get; set; }
public class DestinationManagement
{
public DestinationManagement()
{
Uri = "/";
RequiresAuthentication = false;
}
public string Uri { get; set; }
public bool RequiresAuthentication { get; set; }
}
}
Have you registered the configuration instance which RouteManagement binds against in ConfigureServices method ?
services.Configure<RouteManagement>(Configuration);

Categories