Code samples used in this blog series have been updated to latest version of .NET Core (5.0.4) and GraphQL-Dotnet (4.2.0). Follow this link to get the updated samples.
This post focuses more on configuring a persistent data storage rather than discussing different aspects of GraphQL. With that being said, let's connect to an in-memory database for fake testing.
In our data access layer, we will have a repository. Since it's a good practice to code against abstraction; we will create an interface first for the repository class i.e. IRepository
public interface IRepository
{
Task<IReadOnlyCollection<Item>> GetItems();
Task<Item> GetItemByTag(string tag);
Task<Item> AddItem(Item item);
}
We are already familiar with the GetItemByTag
and AddItem
methods. The GetItems
returns all the items in the inventory. We will add a GraphQL
collection field for that later.
The implementation of the IRepository
is pretty simple as following,
public class Repository : IRepository
{
private ApplicationDbContext _applicationDbContext;
public Repository(ApplicationDbContext applicationDbContext)
{
_applicationDbContext = applicationDbContext;
}
public Task<Item> GetItemByTag(string tag)
{
return _applicationDbContext.Items.FirstAsync(i => i.Tag.Equals(tag));
}
public async Task<IReadOnlyCollection<Item>> GetItems()
{
return await _applicationDbContext.Items.ToListAsync();
}
public async Task<Item> AddItem(Item item)
{
var addedEntity = await _applicationDbContext.Items.AddAsync(item);
return addedEntity.Entity;
}
}
We are using entity framework core, hence the introduction of ApplicationDbContext
. The class extends from the DbContext
of entity framework and contains a single DbSet
for the Item
entity. It will create a table named Items
in memory,
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
public DbSet<Item> Items { get; set; }
}
DbContextOptions
is a way to pass options such as database connection string while configuring ApplicationDbContext
inside ConfigureServices
method of Startup.cs
. In our case, we just have to specify the name of the in-memory database as follow,
public void ConfigureServices(IServiceCollection services)
{
/* Code removed for brevity */
services.AddDbContext<ApplicationDbContext>(options => options.UseInMemoryDatabase("InMemoryDb"));
}
For this, we need to install the Microsoft.EntityFrameworkCore
package. Install this via Nuget
or dotnet-cli
from the official package source,
dotnet add package Microsoft.EntityFrameworkCore -s https://api.nuget.org/v3/index.json -v 3.1.4
AddDbContext<ApplicationDbContext>()
registers the DbContext
with a scoped
service lifetime. Difference between singleton and scope lifetime are,
- A
Singleton
service instance is created only one time (when the application first starts) and the same instance is shared with other services for every subsequent request. - A
Scope
service instance is created every time a new request comes in. It's likesingleton per request
.
The UseInMemoryDatabase()
extension comes from a separate package i.e. Microsoft.EntityFrameworkCore.InMemory
. Install this as well,
dotnet add package Microsoft.EntityFrameworkCore.InMemory -s https://api.nuget.org/v3/index.json -v 3.1.4
Notice, we mentioned Item
as an entity earlier. In order to work with an in-memory database, every entity should have an identity field. The modified Item
class with an identity field is as follows,
public class Item
{
public int Id { get; set; }
public string Tag { get; set; }
public string Title { get; set; }
public decimal Price { get; set; }
}
Time to register the IRepository
with a transient
lifetime,
public void ConfigureServices(IServiceCollection services)
{
/* Code removed for brevity */
services.AddTransient<IRepository, Repository>();
services.AddDbContext<ApplicationDbContext>(options => options.UseInMemoryDatabase("InMemoryDb"));
}
One last thing I want to do is to add a new collection field for showing all the items. The type of the field would be a ListGraphType
of ItemType
,
public class GameStoreQuery : ObjectGraphType
{
public GameStoreQuery(IRepository repository)
{
Field<StringGraphType>(
name: "name",
resolve: context => "Steam"
);
FieldAsync<ItemType>(
"item",
arguments: new QueryArguments(new QueryArgument<NonNullGraphType<StringGraphType>> { Name = "tag" }),
resolve: async context =>
{
var tag = context.GetArgument<string>("tag");
return await repository.GetItemByTag(tag);
}
);
FieldAsync<ListGraphType<ItemType>, IReadOnlyCollection<Item>>(
"items",
resolve: async context =>
{
return await repository.GetItems();
}
);
}
}
Notice, I've used FieldAsync
instead of Field
since we are dealing with Task
. The Mutation is also modified to call the AddItem
of the repository as follows,
public GameStoreMutation(IRepository repository)
{
FieldAsync<ItemType>(
"createItem",
arguments: new QueryArguments(
new QueryArgument<NonNullGraphType<ItemInputType>> { Name = "item" }
),
resolve: async context =>
{
var item = context.GetArgument<Item>("item");
return await repository.AddItem(item);
});
}
Feel free to delete the DataSource.cs
class as it is not needed anymore.
Run the application now and you can fake testing your createItem
mutation field as follows,

Now try to query the items
field and you will see something like the following,

Comments