Mutations

The mutation type in GraphQL is used to mutate/change data. This means that when we are doing mutations, we are intending to cause side-effects in the system.

GraphQL defines mutations as top-level fields on the mutation type. Meaning only the fields on the mutation root type itself are mutations. Everything that is returned from a mutation field represents the changed state of the server.

SDL
type Mutation {
addBook(input: AddBookInput!): AddBookPayload!
publishBook(input: PublishBookInput!): PublishBookPayload!
}

Clients can execute one or more mutations through the mutation type.

GraphQL
mutation {
addBook(input: { title: "C# in depth" }) {
book {
id
title
}
}
publishBook(input: { id: 1 }) {
book {
publishDate
}
}
}

Each of these mutations is executed serially one by one whereas their child selection sets are executed possibly in parallel since only top-level mutation fields (those directly under mutation) are allowed to cause side-effects in GraphQL.

Usage

A mutation type can be defined like the following.

C#
public class Mutation
{
public async Task<BookAddedPayload> AddBook(Book book)
{
// Omitted code for brevity
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddGraphQLServer()
.AddMutationType<Mutation>();
}
// Omitted code for brevity
}

Transactions

With multiple mutations executed serially in one request it can be useful to wrap these in a transaction that we can control.

Hot Chocolate provides for this the ITransactionScopeHandler which is used by the operation execution middleware to create transaction scopes for mutation requests.

Hot Chocolate provides a default implementation based on the System.Transactions.TransactionScope which works with Microsoft ADO.NET data provider and hence can be used in combination with Entity Framework.

The default transaction scope handler can be added like the following.

C#
services
.AddGraphQLServer()
.AddDefaultTransactionScopeHandler();

This is how the default implementation looks like:

C#
/// <summary>
/// Represents the default mutation transaction scope handler implementation.
/// </summary>
public class DefaultTransactionScopeHandler : ITransactionScopeHandler
{
/// <summary>
/// Creates a new transaction scope for the current
/// request represented by the <see cref="IRequestContext"/>.
/// </summary>
/// <param name="context">
/// The GraphQL request context.
/// </param>
/// <returns>
/// Returns a new <see cref="ITransactionScope"/>.
/// </returns>
public virtual ITransactionScope Create(IRequestContext context)
{
return new DefaultTransactionScope(
context,
new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted
}));
}
}

If we implement a custom transaction scope handler or if we choose to extend upon the default transaction scope handler, we can add it like the following.

C#
services
.AddGraphQLServer()
.AddTransactionScopeHandler<CustomTransactionScopeHandler>();