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

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
}
}

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;
}
}

HotChocolate mutation input type uses int instead of ID

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:

Query works Mutations fail: Expected non-null value, resolve delegate return null

I have a GraphQL .Net Core server and queries resolve wonderfully. Mutations however are failing with this error.
"message": "Expected non-null value, resolve delegate return null for \"$GraphQLCore.Types.SInputType\"",
I understand that you do not reuse your query types for mutations and I have created separate types but I'm still missing something.
public class SInputType : InputObjectGraphType
{
public SInputType()
{
Field<IntGraphType>("sid");
...etc
}
}
public class SUpdateMutation : ObjectGraphType
{
MutationMock mm = new MutationMock();
public SUpdateMutation()
{
Field<SInputType>(
"createSrecord",
arguments: new QueryArguments(new QueryArgument<SInputType>
{ Name = "sticker"}),
resolve: context => {
var _stik = context.GetArgument<SModel>("stick");
return mm.StockMutation(stick);
});
}
}
Everything I come up with on Goggle is related to NOT using a InputObjectGraphType but I am and from the examples I see I am using it correctly???
So any input or pointers would be greatly appreciated.
TIA
The error was misleading me.
That error throws when you aren't using the InputObjectGraphType BUT it also throws when you forget to add your new InputType to the sevicesCollection.
Adding this line fixed it.
services.AddSingleton<SInputType>();

Querying single database row using rxjava2

I am using rxjava2 for the first time on an Android project, and am doing SQL queries on a background thread.
However I am having trouble figuring out the best way to do a simple SQL query, and being able to handle the case where the record may or may not exist. Here is the code I am using:
public Observable<Record> createRecordObservable(int id) {
Callable<Record> callback = new Callable<Record>() {
#Override
public Record call() throws Exception {
// do the actual sql stuff, e.g.
// select * from Record where id = ?
return record;
}
};
return Observable.fromCallable(callback).subscribeOn(Schedulers.computation());
}
This works well when there is a record present. But in the case of a non-existent record matching the id, it treats it like an error. Apparently this is because rxjava2 doesn't allow the Callable to return a null.
Obviously I don't really want this. An error should be only if the database failed or something, whereas a empty result is perfectly valid. I read somewhere that one possible solution is wrapping Record in a Java 8 Optional, but my project is not Java 8, and anyway that solution seems a bit ugly.
This is surely such a common, everyday task that I'm sure there must be a simple and easy solution, but I couldn't find one so far. What is the recommended pattern to use here?
Your use case seems appropriate for the RxJava2 new Observable type Maybe, which emit 1 or 0 items.
Maybe.fromCallable will treat returned null as no items emitted.
You can see this discussion regarding nulls with RxJava2, I guess that there is no many choices but using Optional alike in other cases where you need nulls/empty values.
Thanks to #yosriz, I have it working with Maybe. Since I can't put code in comments, I'll post a complete answer here:
Instead of Observable, use Maybe like this:
public Maybe<Record> lookupRecord(int id) {
Callable<Record> callback = new Callable<Record>() {
#Override
public Record call() throws Exception {
// do the actual sql stuff, e.g.
// select * from Record where id = ?
return record;
}
};
return Maybe.fromCallable(callback).subscribeOn(Schedulers.computation());
}
The good thing is the returned record is allowed to be null. To detect which situation occurred in the subscriber, the code is like this:
lookupRecord(id)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Record>() {
#Override
public void accept(Record r) {
// record was loaded OK
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
// there was an error
}
}, new Action() {
#Override
public void run() {
// there was an empty result
}
});

How do I store a comma-separated list in Orchard CMS?

Using Orchard CMS, I am dealing with a record and a part proxy, but cannot figure out how to save it into the DB. In fact, I confess I don't even know how to get the items I'm trying to save into this paradigm. I was originally using enum's for choices:
MyEmum.cs:
public enum Choices { Choice1, Choice2, Choice3, Choice4 }
MyRecord.cs:
public virtual string MyProperty { get; set; }
MyPart.cs:
public IEnumerable<string> MyProperty
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyProperty)) return new string[] { };
return Record
.MyProperty
.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set { Record.MyProperty = value == null ? null : String.Join(",", value); }
}
Now, in my service class, I tried something like:
public MyPart Create(MyPartRecord record)
{
MyPart part = Services.ContentManager.Create<MyPart>("My");
...
part.MyProperty = record.MyProperty; //getting error here
...
return part;
}
However, I am getting the following error: Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable<string>'
Essentially, I am trying to save choices from a checkboxlist (one or more selections) as a comma-separated list in the DB.
And this doesn't even get me over the problem of how do I use the enum. Any thoughts?
For some background:
I understand that the appropriate way to handle this relationship would be to create a separate table and use IList<MyEnum>. However, this is a simple list that I do not intend to manipulate with edits (in fact, no driver is used in this scenario as I handle this on the front-end with a controller and routes). I am just capturing data and redisplaying it in the Admin view for statistical/historical purposes. I may even consider getting rid of the Part (considering the following post: Bertrand's Blog Post.
It should be:
part.MyProperty = new[] {"foo", "bar"};
for example. The part's setter will store the value on the record's property as a comma-separated string, which will get persisted into the DB.
If you want to use enum values, you should use the Parse and ToString APIs that .NET provide on Enum.

Resources