Part-1 shown startup steps like initial configuration of Hot Chocolate GraphQL library into the .Net5 Web API application. This is a continuation part here we are going to understand a few concepts like fetching data from the database, GraphQL mutations, different GraphL queries, etc.
Startup.cs:(ConfigureServices Method)Now in QueryObjectType register this new resolver as a field.
Configure EntityFrameworkCore Database Context:
Now we need to integrate our database into our GraphQL endpoint by creating an entity framework core database context.
Package Manager Command:
Install-Package Microsoft.EntityFrameworkCore -Version 5.0.1
.Net CLI Command:
dotnet add package Micorsoft.EntityFrameworkCore -Version 5.0.1
Package Manager Command:
Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 5.0.1
.Net CLI Command:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer -Version 5.0.1
Now add your connection string into the app settings file.appsettings.Development.json:
"ConnectionStrings":{ "MyWorldDbConnection":"Your_database_Connection_String" }Now let's create our database context.
Data/Context/MyWorldDbContext.cs:
using HC.GraphQL.Sample.Data.Entities; using Microsoft.EntityFrameworkCore; namespace HC.GraphQL.Sample.Data.Context { public class MyWorldDbContext : DbContext { public MyWorldDbContext(DbContextOptions<MyWorldDbContext> options) : base(options) { } public DbSet<Gadgets> Gadgets { get; set; } } }Now register our database context in the startup file.
Startup.cs:(ConfigureServices Method)
services.AddDbContext<MyWorldDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("MyWorldDbConnection")); });
Update Query Resolvers To Use DbContext:
In Part-1 our resolvers serve fake data, now we are going to replace the fake data with our database context which serves data from the database.
SchemaCore/Query.cs:
public Gadgets FirstGadget([Service] MyWorldDbContext context) { return context.Gadgets.FirstOrDefault(); } public List<Gadgets> AllGadgets([Service] MyWorldDbContext context) { return context.Gadgets.ToList(); }
- Here we can observe that DB context injected into the resolver as a parameter this is possible by using the [service] attribute. Hot chocolate provides this 'HotChocolate.ServiceAttribute' to load dependencies into the resolvers.
ObjectTypes/QueryObjectType.cs:
descriptor.Field(g => g.FirstGadget(default)) .Type<GadgetsObjectType>().Name("FirstGadget"); descriptor.Field(g => g.AllGadgets(default)) .Type<ListType<GadgetsObjectType>>().Name("AllGadgets");
Query Arguments To Filter Data:
Now let's implement a new resolver that will filter data based on input parameters received by it.
SchemaCore/Query.cs:
public List<Gadgets> GetByBrand(string brand, [Service] MyWorldDbContext context) { return context.Gadgets.Where(_ => _.Brand.ToLower() == brand.ToLower()).ToList(); }
- Here 'brand' is a query parameter that filters data from the database.
ObjectTypes/QueryObjectType.cs:
descriptor.Field(g => g.GetByBrand(default,default)) .Type<ListType<GadgetsObjectType>>().Name("GetByBrand");Now GraphQL syntax to pass query parameter looks as below.
query{ GetByBrand(brand:"samsung"){ Id ProductName Brand } }
- The 'query' keyword to understand by the GraphQL endpoint either request is for query or mutation.
- The 'GetByBrand' keyword should match with the field name that is registered in QueryObjectType
- The 'brand' is our query parameter.
Aliases:
If we observe our GraphQL response the root object name returns an exact match to the resolver filed name. In the above example 'GetByBrand' name as root property name we can override the root object name by using Aliases names.
Aliases GraphQL request query:
query{ Gadget:GetByBrand(brand:"samsung"){ Id ProductName Brand } }
- Here we can observe 'Gadget' is our alias name for the 'GetByBrand'.
Fragments:
A comparison between two records is very easy using GraphQL endpoint.
A GraphQL query for comparison(without fragments):
query{ Gadget1:GetByBrand(brand:"samsung"){ Id ProductName Brand } Gadget2:GetByBrand(brand:"red mi"){ Id ProductName Brand } }Now test the endpoint.Here in GraphQL endpoint, we are getting comparison data in a perfect way without doing any work at the server-side. But if we carefully observe the queries constructed with duplicate fields, which will be tedious if the fields are high in number.
So to resolve these duplicate fields, GraphQL provided an option called Fragments, a set of similar fields will be grouped to set.
GraphQL comparison query(Using Fragments)
query{ Gadget1:GetByBrand(brand:"samsung"){ ...props } Gadget2:GetByBrand(brand:"red mi"){ ...props } } fragment props on Gadgets{ Id ProductName Brand }
- (Line:11) The keyword 'fragment' defines a query using fragmentation.
- (Line:11) The 'props' is like the name of the fragment.
- (Line:11) The 'Gadgets' is the type of our fragment and it should be matched with our POCO class type in our application it is the 'Gadgets ' class.
- (Line:12-14) All our properties need to requested are defined inside of the fragment.
- (Line: 3&6) Instead of duplicating the request with properties, we replaced the properties with fragments by defining its name 'props'. The 'props' defined as '..prop' because it copies the properties.
Now test the endpoint
Mutations:
GraphQL Mutation represents changing the state of data at the server-side, which means to create or update or delete data.
Now we need to create a model for saving the data. (Note: It is always good to have an individual model for saving the item don't try to use the table model.)
Models/GadgetInput.cs:
namespace HC.GraphQL.Sample.Data.Models { public class GadgetInput { public string ProductName { get; set; } public string Brand { get; set; } public decimal Cost { get; set; } public string Type { get; set; } } }Now this 'GadgetInput' class we want to use to post or save our data but this plain c# class where GraphQL won't understand. So to make GraphQL understand our input type data then we need to create a GraphQL type like 'InputObjectType'.
So let's create an 'InputObjectType' that will map our 'GadgetInput' class.
InputTypes/GadgetInputType.cs:
using HC.GraphQL.Sample.Data.Models; using HotChocolate.Types; namespace HC.GraphQL.Sample.InputTypes { public class GadgetsInputTypes:InputObjectType<GadgetInput> { protected override void Configure(IInputObjectTypeDescriptor<GadgetInput> descriptor) { descriptor.Field(_ => _.ProductName).Type<StringType>().Name("ProductName"); descriptor.Field(_ => _.Brand).Type<StringType>().Name("Brand"); descriptor.Field(_ => _.Cost).Type<DecimalType>().Name("Cost"); descriptor.Field(_ => _.Type).Type<StringType>().Name("Type"); } } }
- (Line: 6) The 'GadgetsInputTypes' that inherits 'HotChocolate.Types.InputObjectType<GadgetInput>'.
- All properties of 'GadgetInput' are registered as fields in GadgetsInputTypes. The reason to create 'GadgetInputTypes' is to capture the request of GraphQL payload and then map the GraphQL object to plain c# class object like 'GadgetInput'. Next, the data in 'GadgetInput' will be saved to the database.
Schema/Mutation.cs:
using HC.GraphQL.Sample.Data.Context; using HC.GraphQL.Sample.Data.Entities; using HotChocolate; namespace HC.GraphQL.Sample.SchemaCore { public class Mutation { public Gadgets Save(GadgetInput input, [Service] MyWorldDbContext context) { Gadgets newgadtet = new Gadgets{ Brand = input.Brand, Cost = input.Cost, ProductName = input.ProductName, Type = input.Type }; context.Gadgets.Add(newgadtet); context.SaveChanges(); return newgadtet; } } }
- Here we have created the resolver method that saves our data to the database.
ObjectTypes/MutationObjectTypes:
using HotChocolate.Types; using HC.GraphQL.Sample.SchemaCore; using HC.GraphQL.Sample.InputTypes; using HC.GraphQL.Sample.Data.Entities; using HC.GraphQL.Sample.Data.Context; namespace HC.GraphQL.Sample.ObjectTypes { public class MutationObjectType:ObjectType<Mutation> { protected override void Configure(IObjectTypeDescriptor<Mutation> descriptor) { descriptor.Field(_ => _.Save(default, default)) .Type<GadgetsObjectType>() .Name("save") .Argument("input", a => a.Type<GadgetsInputTypes>()); } } }
- Here we have implemented ObjectType for our Mutation. Register our resolver as the field. Here name our field as 'save' this name should be used while querying data.
- The using 'Argument' extension method we are reading the payload and the variable name for the payload is 'input'.
- The 'input' name should match while querying the data. For input payload, we are mapping with type 'GadgetsInputTypes'.
Startup.cs:
services.AddGraphQLServer() .AddQueryType<QueryObjectType>() .AddMutationType<MutationObjectType>();GraphQL query syntax for mutation:(Query Part)
mutation ($inputType:GadgetInput ){ save(input:$inputType) { Id } }
- The 'mutation' keyword helps differentiate the request type like query or mutation.
- The '(inpuType:GadgetInput)' this line represents we are defining the type 'GadgetInput'(This our c# input type class created above steps) to '$inputType'(a kind of variable that holds the type).
- The 'save' represents the name of the resolver method registered in the ObjectType class.
- The 'input' is the name of our payload(data we post to the server) and we are defining the payload object type by assigning the '$inputType'.
- The 'Id' is we requesting after a new record saved to the database to return the 'id' of the record
{ "inputType":{ "ProductName":"Think Pad", "Brand":"Samsung", "Cost":7500, "Type": "Laptop" } }
- Here in this object, 'inputType' should match with '$inputType'. here we can observe all the data we want to store.
Support Me!
Buy Me A Coffee
PayPal Me
Wrapping Up:
Hopefully, I think this article delivered some useful information on implementing Hot Chocolate GraphQL in .Net5 Application. I love to have your feedback, suggestions, and better techniques in the comment section below.
You have only implemented CREATE, there doesn't appear to be any UPDATE or DELETE. Despite mentioning it in the text.
ReplyDelete