In this article, we are going to implement a GraphQL Endpoint by using Pure Code First Techniques in the Asp.Net Core application, and database communication will be accomplished by using Dapper Micro ORM.
Schema First: This approach fully involves writing GraphQL Schema Definition Language.
QueryResolver/PersonQueryResolver.cs:
GraphQL:
GraphQL is an open-source data query and manipulation and language for APIs. It is a query language for your API and a server-side runtime for executing queries by using a type system you define for your data. GraphQL can be integrated into any framework like .Net, Java, NestJS, etc and it isn't tied to any specific database or storage engine and is instead backed by your existing code and data.
GraphQL main operations are:
- Query (fetching data)
- Mutation (saving or updating data)
Hot Chocolate GraphQL:
Hot Chocolate is the wrapper library of the original.Net GraphQL library. Hot Chocolate takes the complexity away from building a fully-fledged GraphQL server.
The hot Chocolate library provides 3 different techniques:
- Schema First
- Code First
- Pure Code First
Code First: No schema writing, but every plain c# class should have mapping GraphQL c# class.
Pure Code First: No schema writing, no GraphQL c# classes, only plain c# classes are enough. This approach is very simple, schema generation is taken care of by the GraphQL server. Our demo going to adopt this technique to accomplish the GraphQL endpoint.
Create A .Net5 Web API Application:
Let's create a .Net5 Web API application to accomplish our demo.
Dapper:
Dapper is an Object-Relational Mapping framework for .Net applications. It is a mapping model between the database and .Net objects. The Dapper provides all query and command execution methods as extension methods under the 'System.Data.IDbConnection' interface. The Dapper works as a similar ADO.Net but with much more model mapping support. The Dapper key features are like:
- High performance in query execution.
- Multiple query execution support.
- An easy model mapping between the .Net Object and database result.
Install Dapper And Dependent Packages:
Now install the 'Dapper' package.
Now install the 'System.Data.SqlClient' package.
Create A Sample Service Files:
Let's create sample service files like 'PersonService.cs' and 'IPersonService.cs' where we are going to write our database communication logic using the dapper.
Services/IPersonService.cs:
namespace HotChoco.Dapper.Api.Services { public interface IPersonService { } }Services/PersonService.cs:
using Microsoft.Extensions.Configuration; using System.Data; using System.Data.SqlClient; namespace HotChoco.Dapper.Api.Services { public class PersonService: IPersonService { private readonly IConfiguration _configuration; private IDbConnection Connection { get { return new SqlConnection(_configuration.GetConnectionString("MyWorldDbConnection")); } } public PersonService(IConfiguration configuration) { _configuration = configuration; } } }
- (Line: 11-17) Initialize 'SqlConnection' instance.
Startup.cs:
services.AddScoped<IPersonService, PersonService>();
Install Hot Chocolate GraphQL Library:
Now let's install 'HotChocolate.AspNetCore' package.
Register GraphQL Server And Endpoint:
Now configure the GraphQL server.
Startup.cs:
services.AddGraphQLServer();Configure the GraphQL endpoint.
Startup.cs:
app.UseEndpoints(endpoints => { endpoints.MapGraphQL(); endpoints.MapControllers(); });
Create And Register The Query Resolver File:
Resolver methods are the logical units of the GraphQL endpoints. We have to create individual resolver files for Query and Mutation operations.
Let's create a Query resolver file like 'PersonQueryResolver.cs'.
QueryResolver/PersonQueryResolver.cs:
using HotChocolate.Types; namespace HotChoco.Dapper.Api.QueryResolver { [ExtendObjectType("Query")] public class PersonQueryResolver { } }
- (Line: 5) Our resolver method should be decorated with 'HotChocolate.Types.ExtendObjectType' and we have to pass the name that we have registered globally in the 'Startup.cs'.
Startup.cs:
services.AddGraphQLServer() .AddQueryType(q => q.Name("Query")) .AddType<PersonQueryResolver>();
- (Line: 2) Register the 'AddQueryType' and define a name like 'Query'. This name should be used by all the query resolvers with the help of 'ExtendObjecType'.
- (Line: 3) Registered our 'PersonQueryResolver' type.
Query Single Record:
Let's create a response model for our endpoint like 'PersonDto.cs'.
Dtos/PersonDto.cs:
namespace HotChoco.Dapper.Api.Dtos { public class PersonDto { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Age { get; set; } } }Using dapper let's write our logic to fetch a single record from the database.
Services/IPersonService.cs:
using HotChoco.Dapper.Api.Dtos; namespace HotChoco.Dapper.Api.Services { public interface IPersonService { PersonDto GetFirst(); } }Services/PersonService.cs:
public PersonDto GetFirst() { using (var conn = Connection) { var query = "Select top 1 * from Persons"; var person = conn.Query<PersonDto>(query).FirstOrDefault(); return person; } }
- (Line: 6) Defined raw SQL query.
- (Line: 7) The 'Query<T>' extension executes the specified query against the SQL database and results will be automatically typecasted to specified type 'T'.
QueryResolver/PersonQueryResolver.cs:
public PersonDto GetFirstPerson([Service] IPersonService personService) { return personService.GetFirst(); }
- Methods in the resolver class are the entry points of the GraphQL endpoint.
- (Line: 1) Injecting our 'IPersonService' using the 'HotChocolate.Service' attribute.
query{ firstPerson{ firstName lastName age } }
- (Line: 1) The 'query' keyword represents GraphQL operation type.
- (Line: 2) The 'firstPerson' keyword must match with the name of our resolver method but the first character should be small(which means lower camel case).
- (Line: 3-5) In GraphQL request we can specify payload properties by ourselves only requested will be served by the server. Here also names of the properties should follow the 'Lower Camel Case'.
Query Multiple Records:
Using dapper let's write our logic to fetch a collection of data.
Services/IPersonService.cs:
List<PersonDto> GetAll();Services/PersonService.cs:
public List<PersonDto> GetAll() { using (var conn = Connection) { var query = "Select * from Persons"; var persons = conn.Query<PersonDto>(query).ToList(); return persons; } }
- Here fetching collection of 'Persons' records.
QueryResolver/PersonQueryResolver.cs:
public List<PersonDto> GetAllPerson([Service] IPersonService personService) { return personService.GetAll(); }Sample request query looks like below:
query{ allPerson{ firstName age } }
Query With Filter Parameters:
Using dapper let's add logic to filter the data by 'FirstName' from the database.
Services/IPersonService.cs:
List<PersonDto> FilterByFirstName(string firstName);Services/PersonService.cs:
public List<PersonDto> FilterByFirstName(string firstName) { using(var conn = Connection) { var query = "Select * from Persons where FirstName = @firstName"; var persons = conn.Query<PersonDto>(query, new { firstName }).ToList(); return persons; } }Let's add a resolver method with a filter parameter.
QueryResolver/PersonQueryResolver.cs:
public List<PersonDto> FilterByFirstName(string firstName, [Service] IPersonService personService) { return personService.FilterByFirstName(firstName); }Sample Query with query parameter as below:
query($searchByFirstName:String){ filterByFirstName(firstName:$searchByFirstName){ firstName } }
- Here '$searchByFirstName' is the GraphQL variable name.
{ "searchByFirstName": "naveen" }
Query Complex Object Types:
Let's understand how to fetch the data for complex object types (parent and child-related objects).
Let's create child object class like 'PersonAddressDto.cs'.
Dtos/PersonAddressDto.cs:
namespace HotChoco.Dapper.Api.Dtos { public class PersonAddressDto { public int PersonAddressId { get; set; } public string AddressLine1 { get; set; } public string AddressLine2 { get; set; } public string City { get; set; } public string State { get; set; } public string Country { get; set; } public int PersonId { get; set; } } }
Now update the 'PersonDto' with a navigation peroperty of type 'PersonAddressDot'.
Dtos/PersonDto.cs:
using System.Collections.Generic; namespace HotChoco.Dapper.Api.Dtos { public class PersonDto { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Age { get; set; } public List<PersonAddressDto> Address { get; set; } } }
Using dapper let's write logic to fetch multiple result sets from the database.
Services/IPersonService.cs:
List<PersonDto> GetPersonAddress();Services/PersonService.cs:
public List<PersonDto> GetPersonAddress() { using (var conn = Connection) { var query = @" Select * From Persons Select * From PersonAddress "; var result = conn.QueryMultiple(query); var persons = result.Read<PersonDto>().ToList(); var address = result.Read<PersonAddressDto>().ToList(); foreach (var person in persons) { person.Address = address.Where(_ => _.PersonId == person.ID).ToList(); } return persons; } }
- Here we are fetching 2 result sets like 'Persons' and 'PersonAddres' using the dapper 'QueryMultiple' method.
QueryResolver/PersonQueryResolver.cs:
public List<PersonDto> GetPersonAddress([Service] IPersonService personService) { return personService.GetPersonAddress(); }Sample Query looks like below:
query{ personAddress{ lastName address{ addressLine1 addressLine1 city state country } } }
- (Line: 4-10) specifying required properties of the child object(address object). In this way, we can fetch the child object properties in a relational object response.
Create And Register Mutation Resolver File:
Let's create the Mutation resolver file like 'PersonMutationResolver.cs'.
MutationResolver/PersonMutationResolver.cs:
using HotChocolate.Types; namespace HotChoco.Dapper.Api.MutationResolver { [ExtendObjectType("Mutation")] public class PersonMutationResolver { } }
- The 'Mutation' name here we used should be matched with the root mutation name that we are going to register in the Startup.cs.
Startup.cs:
services.AddGraphQLServer() .AddQueryType(q => q.Name("Query")) .AddType<PersonQueryResolver>() .AddMutationType(m => m.Name("Mutation")) .AddType<PersonMutationResolver>();
- (Line: 4) Registered root name like 'Mutation' for our GraphQL Mutation type, this name needs to be decorated by every mutation resolver method in our application.
Mutation To Save A Record:
GraphQL Mutation operations are to save the data to the database. Let's implement our save logic using the dapper.
Services/IPersonService.cs:
int SavePerson(PersonDto person)Services/PersonService.cs:
public int SavePerson(PersonDto person) { using(var conn = Connection) { var command = @"INSERT INTO Persons(LastName, FirstName, Age) VALUES (@LastName, @FirstName, @Age)"; var saved = conn.Execute(command, param: person); return saved; } }Let's add our mutation resolver method as below:
MutationResolver/PersonMutationResolver.cs:
public int SavePerson(PersonDto person,[Service] IPersonService personService) { return personService.SavePerson(person); }The mutation sample looks as below:
mutation($personData:PersonDtoInput){ savePerson(person:$personData) }
- The '$personData' is a GraphQL variable name. The 'PersonDtoInput' is a GraphQL generated input type based on our resolver method input variable type that is 'PersonDto'.
{ "personData": { "age": "29", "firstName": "Gopi", "lastName": "P", "iD": 0 } }
Support Me!
Buy Me A Coffee
PayPal Me
Video Session:
Wrapping Up:
Hopefully, I think this article delivered some useful information integrating HotChocolate GraphQL endpoint into the Asp.Net application using Dapper Micro ORM. I love to have your feedback, suggestions, and better techniques in the comment section below.
thanks for this article my friend grettings from Chile
ReplyDelete