8

GraphQL with .NET Core (Part - X: Execution Strategies)

 3 years ago
source link: https://www.fiyazhasan.me/graphql-with-net-core-part-x-execution-strategies/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
September 6, 2020

GraphQL with .NET Core (Part - X: Execution Strategies)

GraphQL with .NET Core (Part - X: Execution Strategies)

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.

With the updated version (4.2.0) of GraphQL-Dotnet, the SubscriptionExecutionStrategy feature is no longer a part of the core library. It's shipped as a stand-alone NuGet package,

Install-Package GraphQL.SystemReactive -Version 4.2.0

Faking with an in-memory database is simple and easy but sooner or later you are going to need a real database. A typical SQL server database setup is just a connection string away. Modify the EF Core DBContext service registration and pass a connection string of a SQL Server instance as an option,

public static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options => options.UseLoggerFactory(loggerFactory).UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=GameStoreDb;Trusted_Connection=True;"), ServiceLifetime.Transient);
}
Startup.cs

The logger is configured to log the result of various sql queries generated within the application life-cycle.

m.PNG

We do have a problem with our application. And that is,

Entity framework can't execute queries in parallel. So we use async/await to finish running one operation before starting another.

Execution Strategies

By default, graphql-dotnet executes queries in parallel. Most of the time that's the desired behavior. But there is a catch when a parent graph type has dependencies on a child graph type which also uses asynchronous tasks to resolve field values. So even if the parent task is awaited in ExecuteNodeAsync, the child task will not await this call all the time.

And we will sometimes get exceptions like,

System.InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext.

Let's take the Orders query for example. Along with all the orders, if we intend to get the customer for those orders, we will query for something like,

query GetOrders {
  orders {
    tag
    createdAt
    customer {
      name
      billingAddress
    }
  }
}

The application may or may not (most of the time will) throw this following exception,

o.PNGn-1.png

Notice that because of parallel execution of the queries, the customer field for the second order evaluates to null.

We can change this execution behavior by implementing our document executer and replace it with the default DocumentExecuter. The following snippet does that for you,

public class SerialDocumentExecuter : DocumentExecuter
{
    private static IExecutionStrategy ParallelExecutionStrategy = new ParallelExecutionStrategy(); 
    private static IExecutionStrategy SerialExecutionStrategy = new SerialExecutionStrategy();
    private static IExecutionStrategy SubscriptionExecutionStrategy = new SubscriptionExecutionStrategy();

    protected override IExecutionStrategy SelectExecutionStrategy(ExecutionContext context)
    {
        return context.Operation.OperationType switch
        {
            OperationType.Query => SerialExecutionStrategy,
            OperationType.Mutation => SerialExecutionStrategy,
            OperationType.Subscription => SubscriptionExecutionStrategy,
            _ => throw new InvalidOperationException($"Unexpected OperationType {context.Operation.OperationType}"),
        };
    }
}
SerialDocumentExecuter.cs

Notice, instead of ParallelExecutionStrategy, we intend to go for SerialExecutionStrategy for every query and mutation.

All done except for registering this implementation of DocumentExecuter in the IOC,

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDocumentExecuter, SerialDocumentExecuter>();
}
Startup.cs

Repository Link

Part-X

6568968?s=400&v=4

Important Links

13958777?s=400&v=4

Share: Twitter Facebook LinkedIn

Comments


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK