Introduction
In the previous article of the GraphQL series, we discussed the introduction of GraphQL and Pros & Cons of using GraphQL. If you haven’t read my previous articles, then I highly encourage you to do so.
I believe you have a prior understanding of EF Core and we'll be concentrating only on GraphQL concepts. If not, please refer to the below article.
Coding
Let's directly jump into coding section by adding NuGet package
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Design
- Microsoft.EntityFrameworkCore.Tools
- GraphQL
- GraphQL.Server.Transports.AspNetCore
- GraphQL.Server.UI.Playground
In this article, we are illustrating a model that is concerned with books and their authors.
- public class Author
- {
- public int AuthorId { get; set; }
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public ICollection<Book> Books { get; set; }
- }
- public class Book
- {
- public int BookId { get; set; }
- public string Title { get; set; }
- public int PublicationYear { get; set; }
- public int AuthorId { get; set; }
- public Author Author { get; set; }
- }
- public class AuthorContext:DbContext
- {
- public DbSet<Author> Authors { get; set; }
- public DbSet<Book> Books { get; set; }
- public AuthorContext(DbContextOptions dbContextOptions) :base(dbContextOptions)
- {
- }
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- modelBuilder.Entity<Author>().HasData(new Author
- {
- FirstName= "Agatha",
- LastName="Christry",
- AuthorId = 1
- });
- modelBuilder.Entity<Book>().HasData(
- new Book { Title = "The Mysterious Affair at Styles", PublicationYear = 1921,AuthorId=1,BookId=1 },
- new Book { Title = "The Secret Adversary", PublicationYear = 1922,AuthorId = 1,BookId=2 }
- );
- }
- }
I highly encourage you to use IEntityTypeConfiguration for data seeding rather than using OnModelCreating method. You can refer to the article here.
- public interface IAuthorRepository
- {
- Task<IEnumerable<Author>> GetAuthorsAsync();
- Task<Author> GetAuthorAsync(int authorId);
- Task<Author> InsertAuthorAsync(Author author);
- Task<Author> GetAuthorByFirstNameAsync(string firstName);
- }
- public class AuthorRepository: IAuthorRepository
- {
- private readonly AuthorContext authorContext;
- public AuthorRepository(AuthorContext authorContext)
- {
- this.authorContext = authorContext;
- }
- public async Task<Author> GetAuthorAsync(int authorId)
- {
- return await authorContext.Authors.FirstOrDefaultAsync(x => x.AuthorId == authorId);
- }
- public async Task<Author> GetAuthorByFirstNameAsync(string firstName)
- {
- return await authorContext.Authors.FirstOrDefaultAsync(x => x.FirstName == firstName);
- }
- public async Task<IEnumerable<Author>> GetAuthorsAsync()
- {
- return await authorContext.Authors.ToListAsync();
- }
- public async Task<Author> InsertAuthorAsync(Author author)
- {
- var result = (await authorContext.Authors.AddAsync(author)).Entity;
- await authorContext.SaveChangesAsync();
- return result;
- }
- }
- public interface IBookRepository
- {
- Task<IEnumerable<Book>> GetBooksAsync();
- Task<IEnumerable< Book>> GetBooks(int authorId);
- Task<Book> InsertBook(Book book);
- }
- public class BookRepository: IBookRepository
- {
- private readonly AuthorContext authorContext;
- public BookRepository(AuthorContext authorContext)
- {
- this.authorContext = authorContext;
- }
- public async Task<IEnumerable< Book>> GetBooks(int authorId)
- {
- var result = await (from author in authorContext.Set<Author>()
- join book in authorContext.Set<Book>() on author.AuthorId equals book.AuthorId
- where author.AuthorId==authorId
- select book).ToListAsync();
- return result;
- }
- public async Task<IEnumerable<Book>> GetBooksAsync()
- {
- return await authorContext.Books.ToListAsync();
- }
- public async Task<Book> InsertBook(Book book)
- {
- var result = (await authorContext.Books.AddAsync(book)).Entity;
- await authorContext.SaveChangesAsync();
- return result;
- }
- }
- services.AddDbContext<AuthorContext>(x => x.UseSqlServer(Configuration.GetConnectionString("SqlConnectionString")));
- services.AddScoped<IAuthorRepository, AuthorRepository>();
- services.AddScoped<IBookRepository, BookRepository>();
- //Adding migration
- dotnet ef migrations add InitialMigration
- //Update Database
- dotnet ef database update
GraphQL Middleware
You can add GraphQL middleware to the configure method of the Startup class
- app.UseGraphQL<AuthorSchema>("/api/author");
- app.UseGraphQL<BookSchema>("/api/book");
- app.UseGraphQLPlayground(new GraphQLPlaygroundOptions());
- app.UseAuthorization();
A GraphQL operation can be either read or write, however, Query is used to read or fetch values, whereas Mutation is used to write or post values.
Since you want to get data about authors and books, you need to query the author and book classes. To make the author & book class in GraphQL queryable, you should create a new type and extend it from custom ObjectGraphType<T>.
- public class AuthorType:ObjectGraphType<Author>
- {
- public AuthorType(IBookRepository bookRepository)
- {
- Field(x => x.AuthorId).Description("Author Id");
- Field(x => x.FirstName).Description("Author's first name");
- Field(x => x.LastName);
- Field<ListGraphType<BookType>>("books",
- arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "authorId" }),
- resolve: context =>
- {
- return bookRepository.GetBooks(context.Source.AuthorId);
- });
- }
- }
- public class BookType:ObjectGraphType<Book>
- {
- public BookType()
- {
- Field(x => x.BookId);
- Field(x => x.Title);
- Field(x => x.PublicationYear);
- }
- }
You can now write a GraphQL query that will handle fetching an author or list of authors.
- public class AuthorQuery:ObjectGraphType<object>
- {
- public AuthorQuery(IAuthorRepository authorRepository)
- {
- Field<ListGraphType<AuthorType>>("authors", resolve:context =>
- {
- return authorRepository.GetAuthorsAsync();
- });
- Field<AuthorType>("author",
- arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "authorId" }),
- resolve: context =>
- {
- return authorRepository.GetAuthorAsync(context.GetArgument<int>("authorId"));
- });
- }
- }
- public class BookQuery:ObjectGraphType
- {
- public BookQuery(IBookRepository bookRepository)
- {
- Field<ListGraphType<BookType>>("books", resolve: context =>
- {
- return bookRepository.GetBooksAsync();
- });
- }
- }
- public class AuthorSchema:Schema
- {
- public AuthorSchema(IDependencyResolver resolver):base(resolver)
- {
- Query = resolver.Resolve<AuthorQuery>();
- }
- }
- public class BookSchema:Schema
- {
- public BookSchema(IDependencyResolver resolver):base(resolver)
- {
- Query = resolver.Resolve<BookQuery>();
- }
- }
- services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));
- services.AddScoped<AuthorSchema>();
- services.AddScoped<BookSchema>();
- services.AddGraphQL(o => o.ExposeExceptions = false)
- .AddGraphTypes(ServiceLifetime.Scoped);
- services.Configure<KestrelServerOptions>(options =>
- {
- options.AllowSynchronousIO = true;
- });
- query {
- author(authorId:1){
- firstName
- lastName
- authorId
- books {
- publicationYear
- title
- bookId
- }
- }
- }

GraphQL Mutation
As discussed before, Mutation is used to write or post values. You have to define InputObjectGraphType for author and book types.
- public class AuthorInputType: InputObjectGraphType
- {
- public AuthorInputType()
- {
- Name = "authorInput";
- Field<NonNullGraphType<StringGraphType>>("firstName");
- Field<NonNullGraphType<StringGraphType>>("lastName");
- }
- }
- public class BookInputType:InputObjectGraphType
- {
- public BookInputType()
- {
- Name = "bookInput";
- Field<NonNullGraphType<StringGraphType>>("title");
- Field<NonNullGraphType<IntGraphType>>("publicationYear");
- Field<IntGraphType>("authorId");
- }
- }
You can define mutation for author and book using ObjectGraphType
- public class AuthorMutation:ObjectGraphType
- {
- public AuthorMutation(IAuthorRepository authorRepository,IBookRepository bookRepository)
- {
- Field<AuthorType>("insertAuthor",
- arguments: new QueryArguments(new QueryArgument<AuthorInputType> { Name = "author" }),
- resolve:context=>
- {
- return authorRepository.InsertAuthorAsync(context.GetArgument<Author>("author"));
- });
- }
- }
- public class BookMutation:ObjectGraphType
- {
- private readonly IBookRepository bookRepository;
- private readonly IAuthorRepository authorRepository;
- public BookMutation(IBookRepository bookRepository,IAuthorRepository authorRepository)
- {
- this.bookRepository = bookRepository;
- this.authorRepository = authorRepository;
- Field<BookType>("insertAuthorAndBook",
- arguments: new QueryArguments(
- new QueryArgument<AuthorInputType> { Name = "author" },
- new QueryArgument<BookInputType> { Name = "book" }),
- resolve: context =>
- {
- var author = context.GetArgument<Author>("author");
- var book = context.GetArgument<Book>("book");
- return InsertBook(author, book);
- });
- Field<BookType>("insertBook",
- arguments: new QueryArguments(new QueryArgument<BookInputType> { Name = "book" }),
- resolve: context =>
- {
- return bookRepository.InsertBook(context.GetArgument<Book>("book"));
- });
- }
- private async Task<Book> InsertBook(Author author,Book book)
- {
- if (author == null)
- return null;
- var existingAuthor =await authorRepository.GetAuthorByFirstNameAsync(author.FirstName);
- if (existingAuthor == null)
- {
- var newAuthor = await authorRepository.InsertAuthorAsync(author);
- book.AuthorId = newAuthor.AuthorId;
- return await bookRepository.InsertBook(book);
- }
- else
- {
- book.AuthorId = existingAuthor.AuthorId;
- return await bookRepository.InsertBook(book);
- }
- }
- }
Finally, you need to define mutation in both the author and book schema classes
- public class AuthorSchema:Schema
- {
- public AuthorSchema(IDependencyResolver resolver):base(resolver)
- {
- Query = resolver.Resolve<AuthorQuery>();
- Mutation = resolver.Resolve<AuthorMutation>();
- }
- }
- public class BookSchema:Schema
- {
- public BookSchema(IDependencyResolver resolver):base(resolver)
- {
- Query = resolver.Resolve<BookQuery>();
- Mutation = resolver.Resolve<BookMutation>();
- }
- }
You can use the following query to insert a record
- mutation($author:authorInput!,$book:bookInput!){
- insertAuthor(author:$author){
- firstName
- lastName
- }
- insertBook(book:$book){
- title,
- publicationYear
- }
- }
- {
- "author":{
- "firstName":"Charles",
- "lastName":"Darwin"
- },
- "book": {
- "title": "On the origin of Species",
- "publicationyear": 1856
- }
- }
Everything seems to be working fine, but there are still issues with GraphQL endpoint. GraphQL should have a single endpoint and currently, there are 2 endpoints for author and book.
In the next article, we will discuss fixing the endpoint issue by introducing GraphQLController and will also see how the client has to invoke the API.
I hope you liked the article. In case you find the article interesting, please kindly like and share it.

Join the conversation! Your thoughts help the community grow.