HotChocolate mutation input type uses int instead of ID - graphql

I am new to HotChocolate and GraphQL as a whole and am trying to grasp on enabling Nodes and/or Relay support for my GraphQL API. Currently using HotChocolate v12.
Context
I am trying to create a mutation that updates an entity (Client in this example). I am using code-first approach here and have the following:
An input record/class is defined as follows:
public record UpdateClientInput([ID(nameof(Client))] int Id, string Code, string Name, string Subdomain);
The mutation function which returns the payload class:
[UseAppDbContext]
public async Task<UpdateClientPayload> UpdateClientAsync(UpdateClientInput input, [ScopedService] AppDbContext context)
{
var client = await context.Set<Client>().FirstOrDefaultAsync(x => x.Id == input.Id);
// cut for brevity
return new UpdateClientPayload(client);
}
I have enabled support for Nodes by adding this in the services configuration:
builder.Services
.AddGraphQLServer()
.AddQueryType(d => d.Name("Query"))
.AddMutationType(d => d.Name("Mutation"))
// removed others for brevity
.AddGlobalObjectIdentification()
.AddQueryFieldToMutationPayloads();
But yet when I browse the API using Banana Cake Pop, the UpdateClientInput object in the schema definition still uses int instead of the ID! (see screenshot below). So the GraphQL client I am using (Strawberry Shake) generates the input object that does not use the ID! type. Am I missing something here?

So to solve this problem, this was actually a change in HotChocolate v12 where records should use the [property: ID] instead of the [ID] attribute. That change is found here https://chillicream.com/docs/hotchocolate/api-reference/migrate-from-11-to-12#records.
What I did is to change the record declaration from:
public record UpdateClientInput([ID(nameof(Client))] int Id, string Code, string Name, string Subdomain);
to
public record UpdateClientInput([property: ID] int Id, string Code, string Name, string Subdomain);
that generated this input object as:

Related

Hot Chocolate top-level properties from multiple sources

I have an entity (Customer) that needs to pull data from multiple sources. The schema looks roughly like this:
{
id: string
name: string
address: string
contact: string
status: string
}
The id, name and address come from an EF datacontext. The contact and status fields come from a single REST endpoint, and looks like this:
GET /url/customer?id=1234
{
id: '1234'
contact: 'joe#bloggington.com'
status: 'ACTIVE'
}
If I put both contact and status into a single field/object (i.e. ContactStatus), then it would be a simple case of creating an extension for Customer. But these fields are not related, and should be regarded as different top-level fields.
Is there a way to ensure that the REST endpoint is called only once, when fetching all values? Essentially resolving both fields when fetching one or the other maybe?
Hot Chocolate v12.15.0, net6.0
Yes you can use the batching api to do this
Create a DataLoader that loads the data from the rest endpoint. This way you can also optimize the fetches from the rest endpoint (if the endpoint supports somthing like /url/customer?ids=1234,2345,5930)
e.g. class YourDataloader extends BatchDataLoader<int, AdditionalCustomerData>
Then you can just do
[ExtendObjectType<Customer>]
public class CustomerExtensions
{
public Task<string> GetContactAsync(
[Parent]Customer customer,
YourDataloader dataloader)
{
var result = await dataloader.LoadAsync(customer.Id);
return result.Contact;
}
public Task<Status> GetStatusAsync(
[Parent]Customer customer,
YourDataloader dataloader)
{
var result = await dataloader.LoadAsync(customer.Id);
return result.Status;
}
}

Running GraphQL query returns The ID `1` has an invalid format

Following the Hot Chocolate workshop and after the 4th step, when running the query
query GetSpecificSpeakerById {
a: speakerById(id: 1) {
name
}
b: speakerById(id: 1) {
name
}
}
I'm getting the following error.
The ID `1` has an invalid format.
Also, the same error is thrown for all queries which have ID as a parameter, maybe this could be a hint, what to check, for me, a person, who just run the workshop it's still unclear.
Based on (not accepted) answer in similar question Error "The ID `1` has an invalid format" when querying HotChocolate, I've checked Relay and it's configuration and looks good.
DI
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(CreateAutomapper());
services.AddPooledDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlite(CONNECTION_STRING).UseLoggerFactory(ApplicationDbContext.DbContextLoggerFactory));
services
.AddGraphQLServer()
.AddQueryType(d => d.Name(Consts.QUERY))
.AddTypeExtension<SpeakerQueries>()
.AddTypeExtension<SessionQueries>()
.AddTypeExtension<TrackQueries>()
.AddMutationType(d => d.Name(Consts.MUTATION))
.AddTypeExtension<SpeakerMutations>()
.AddTypeExtension<SessionMutations>()
.AddTypeExtension<TrackMutations>()
.AddType<AttendeeType>()
.AddType<SessionType>()
.AddType<SpeakerType>()
.AddType<TrackType>()
.EnableRelaySupport()
.AddDataLoader<SpeakerByIdDataLoader>()
.AddDataLoader<SessionByIdDataLoader>();
}
Speaker type
public class SpeakerType : ObjectType<Speaker>
{
protected override void Configure(IObjectTypeDescriptor<Speaker> descriptor)
{
descriptor
.ImplementsNode()
.IdField(p => p.Id)
.ResolveNode(WithDataLoader);
}
// implementation
}
And query itself
[ExtendObjectType(Name = Consts.QUERY)]
public class SpeakerQueries
{
public Task<Speaker> GetSpeakerByIdAsync(
[ID(nameof(Speaker))] int id,
SpeakerByIdDataLoader dataLoader,
CancellationToken cancellationToken) => dataLoader.LoadAsync(id, cancellationToken);
}
But without a bit of luck. Is there something else, what could I check? The full project is available on my GitHub.
I see you enabled relay support on this project.
The endpoint execpts a valid relay ID.
Relay exposes opaque IDs to the client. You can read more about it here:
https://graphql.org/learn/global-object-identification/
In short, a Relay ID is a base64 encoded combination of the typename and the id.
To encode or decode in the browser you can simply use atob and btoa on the console.
So the id "U3BlYWtlcgppMQ==" contains the value
"Speaker
i1"
you can decode this value in the browser with btoa("U3BlYWtlcgppMQ==") and encode the string with
atob("Speaker
i1")
So this query will work:
query GetSpecificSpeakerById {
a: speakerById(id: "U3BlYWtlcgppMQ==") {
id
name
}
}

Using implement in GraphQL without any arguments

If I want to use interface/implements without any arguments, how would I do that?
My current code looks like this:
interface User {
GUID: String
name: String
age: Int
}
type FacebookUser implements User
type GoogleUser implements User
The reason I'm doing it like this, is to handle FacebookUsers age getting and GoogleUsers age getting different in the Resolvers-function.
And if they aren't asking for age, I don't want to get them at all.
If I add this, it works:
interface User {
GUID: String
name: String
age: Int
}
type FacebookUser implements User {
bogus: String
}
type GoogleUser implements User {
bogus: String
}
But there is nothing that separates the users except their underlying system, so I don't want to add specific fields to them.
I'm using Apollo GraphQL Tools 3.0.2

what is a projection in LINQ, as in .Select()

I typically do mobile app development, which doesn't always have .Select. However, I've seen this used a bit, but I don't really know what it does or how it's doing whatever it does. It is anything like
from a in list select a // a.Property // new Thing { a.Property}
I'm asking because when I've seen code using .Select(), I was a bit confused by what it was doing.
.Select() is from method syntax for LINQ, select in your code from a in list select a is for query syntax. Both are same, query syntax compiles into method syntax.
You may see: Query Syntax and Method Syntax in LINQ (C#)
Projection:
Projection Operations - MSDN
Projection refers to the operation of transforming an object into a
new form that often consists only of those properties that will be
subsequently used. By using projection, you can construct a new type
that is built from each object. You can project a property and perform
a mathematical function on it. You can also project the original
object without changing it.
You may also see:
LINQ Projection
The process of transforming the results of a query is called
projection. You can project the results of a query after any filters
have been applied to change the type of the collection that is
returned.
Example from MSDN
List<string> words = new List<string>() { "an", "apple", "a", "day" };
var query = from word in words
select word.Substring(0, 1);
In the above example only first character from each string instance is selected / projected.
You can also select some fields from your collection and create an anonymous type or an instance of existing class, that process is called projection.
from a in list select new { ID = a.Id}
In the above code field Id is projected into an anonymous type ignoring other fields. Consider that your list has an object of type MyClass defined like:
class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
Now you can project the Id and Name to an anonymous type like:
Query Syntax:
var result = from a in list
select new
{
ID = a.Id,
Name = a.Name,
};
Method Syntax
var result = list.Select(r => new { ID = r.Id, Name = r.Name });
You can also project result to a new class. Consider you have a class like:
class TemporaryHolderClass
{
public int Id { get; set; }
public string Name { get; set; }
}
Then you can do:
Query Syntax:
var result = from a in list
select new TemporaryHolderClass
{
Id = a.Id,
Name = a.Name,
};
Method Syntax:
var result = list.Select(r => new TemporaryHolderClass
{
Id = r.Id,
Name = r.Name
});
You can also project to the same class, provided you are not trying to project to classes generated/created for LINQ to SQL or Entity Framework.
My summary is it takes results (or a subset of results) and allows you to quickly restructure it for use in the local context.
The select clause produces the results of the query and specifies the
"shape" or type of each returned element. For example, you can specify
whether your results will consist of complete Customer objects, just
one member, a subset of members, or some completely different result
type based on a computation or new object creation.
Source: http://msdn.microsoft.com/en-us/library/bb397927.aspx
There are a lot of possible uses for this but one is taking a complex object which of many other contains a property that is a string -- say Name -- and allows you to return an enumeration with just the entries of Name. I believe you can also do the opposite -- use that property ( for example) and create / return new type of object while passing in a property or properties.
It means "mapping". Map each element of a sequence to a transformed sequence. I hadn't comprehended its meaning before I looked at the image.
Where does the meaning of the word come from?
Simply, math! https://mathworld.wolfram.com/Projection.html

Azure Table Storage, WCF Service and Enum

Here's my problem. A class which defines an order has a property called PaymentStatus, which is an enum defined like so:
public enum PaymentStatuses : int
{
OnDelivery = 1,
Paid = 2,
Processed = 3,
Cleared = 4
}
And later on, in the class itself, the property definition is very simple:
public PaymentStatuses? PaymentStatus { get; set; }
However, if I try to save an order to the Azure Table Storage, I get the following exception:
System.InvalidOperationException: The type Order+PaymentStatuses' has no settable properties.
At this point I thought using enum isn't possible, but a quick Google search returned this: http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/7eb1a2ca-6c1b-4440-b40e-012db98ccb0a
This page lists two answers, one of which seems to ignore the problems and suggests that using an enum in Azure Storage is fine.
Now, I don't NEED to store the enum in the Azure Table Storage as such, I could just as well store a corresponding int, however, I do need this property to be exposed in the WCF service.
I've tried making the property use get and set to return the enum from a stored integer, and remove this property from Azure by using the WritingEntity event on my DataContext, but I get that exception before the event for this entity is fired.
At this point, I'm at a loss, I don't know what else I can do to have this property in WCF as an enum, but have Azure store just the int.
Enum is not supported. Even though it is defined like an int, it is really not an integral type supported by Table Storage. Here is the list of types supported. An enum is just a string expression of an integral number with an object-oriented flavor.
You can store int in table storage and then convert it using Enum.Parse.
Here's a simple workaround:
public int MyEnumValue { get; set; } //for use by the Azure client libraries only
[IgnoreProperty] public MyEnum MyEnum
{
get { return (MyEnum) MyEnumValue; }
set { MyEnumValue = (int) value; }
}
It would have been nicer if a simple backing value could have been employed rather than an additional (public!) property - without the hassle of overriding ReadEntity/WriteEntity of course. I opened a user voice ticket that would facilitate that, so you might want to upvote it.
ya i was having this same problem
i changed my property which was earlier enum to int. now this int property parses the incoming int and saves it into a variale of the same enum type so now the code that was
public CompilerOutputTypes Type
{get; set;}
is chaged to
private CompilerOutputTypes type;
public int Type
{
get {return (int)type;}
set { type = (CompilerOutputTypes)value; }
}
Just suggestions...
I remember that in WCF you have to mark enums with special attributes: http://msdn.microsoft.com/en-us/library/aa347875.aspx
Also, when you declare PaymentStatuses? PaymentStatus, you are declaring Nullable<PaymentStatuses> PaymentStatus. The ? sintax is just syntactic sugar. Try to remove the ? and see what happen (you could add a PaymentStatuses.NoSet = 0 , because the default value for an Int32 is 0).
Good luck.
Parvs solution put me on the right track but I had some minor adjustments.
private string _EnumType;
private EnumType _Type;
//*********************************************
//*********************************************
public string EnumType
{
get { return _Type.ToString(); }
set
{
_EnumType = value;
try
{
_Type = (EnumType)Enum.Parse(typeof(EnumType), value);
}
catch (Exception)
{
_EnumType = "Undefined";
_Type = [mynamespace].EnumType.Undefined;
}
}
}
I have come across a similar problem and have implemented a generic object flattener/recomposer API that will flatten your complex entities into flat EntityProperty dictionaries and make them writeable to Table Storage, in the form of DynamicTableEntity.
Same API will then recompose the entire complex object back from the EntityProperty dictionary of the DynamicTableEntity.
This is relevant to your question because the ObjectFlattenerRecomposer API supports flattening property types that are normally not writeable to Azure Table Storage like Enum, TimeSpan, all Nullable types, ulong and uint by converting them into writeable EntityProperties.
The API also handles the conversion back to the original complex object from the flattened EntityProperty Dictionary. All that the client needs to do is to tell the API, I have this EntityProperty Dictionary that I just read from Azure Table (in the form of DynamicTableEntity.Properties), can you convert it to an object of this specific type. The API will recompose the full complex object with all of its properties including 'Enum' properties with their original correct values.
All of this flattening and recomposing of the original object is done transparently to the client (user of the API). Client does not need to provide any schema or any knowledge to the ObjectFlattenerRecomposer API about the complex object that it wants to write, it just passes the object to the API as 'object' to flatten it. When converting it back, the client only needs to provide the actual type of object it wants the flattened EntityProperty Dictionary to be converted to. The generic ConvertBack method of the API will simply recompose the original object of Type T and return it to the client.
See the usage example below. The objects do not need to implement any interface like 'ITableEntity' or inherit from a particular base class either. They do not need to provide a special set of constructors.
Blog: https://doguarslan.wordpress.com/2016/02/03/writing-complex-objects-to-azure-table-storage/
Nuget Package: https://www.nuget.org/packages/ObjectFlattenerRecomposer/
Usage:
//Flatten object (ie. of type Order) and convert it to EntityProperty Dictionary
Dictionary<string, EntityProperty> flattenedProperties = EntityPropertyConverter.Flatten(order);
// Create a DynamicTableEntity and set its PK and RK
DynamicTableEntity dynamicTableEntity = new DynamicTableEntity(partitionKey, rowKey);
dynamicTableEntity.Properties = flattenedProperties;
// Write the DynamicTableEntity to Azure Table Storage using client SDK
//Read the entity back from AzureTableStorage as DynamicTableEntity using the same PK and RK
DynamicTableEntity entity = [Read from Azure using the PK and RK];
//Convert the DynamicTableEntity back to original complex object.
Order order = EntityPropertyConverter.ConvertBack<Order>(entity.Properties);

Resources