Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

C# GraphQL client with Linq-like syntax

License

NotificationsYou must be signed in to change notification settings

byme8/ZeroQL

Repository files navigation

🚀 Welcome to ZeroQL, a high-performance C#-friendly GraphQL client! 🎉

ZeroQL makes it easy to perform queries and mutations with Linq-like syntax. Unlike other GraphQL clients, ZeroQL doesn't require Reflection.Emit or expressions, which means the runtime provides performance very close to a raw HTTP call.

Features

Here's a quick rundown of what ZeroQL can do at the moment:

You can find the full wikihere or just by clicking on the feature bullet point you are interested in.

Check out our articles to learn more about ZeroQL:

How to setup

Here, you can find the setup for net6.0+ projects.You can findnetstandard or .Net Framework andUnity setup inwiki.

The initial setup:

# create console appdotnet new console -o QLClient# go to project foldercd QLClient# create manifest file to track nuget toolsdotnet new tool-manifest# add ZeroQL.CLI nuget tooldotnet tool install ZeroQL.CLI# or 'dotnet tool restore' once you pulled the existing repository# add ZeroQL nuget packagedotnet add package ZeroQL# fetch graphql schema from server(creates schema.graphql file)dotnet zeroql schema pull --url http://localhost:10000/graphql# to create ZeroQL config file: ./config.zeroql.jsondotnet zeroql config init# build the project to initiate the ZeroQL client generation with options specified inside config.zeroql.jsondotnet build

The build should be successful, and now we can use the generated client.

Config

The commanddotnet zeroql config init creates theconfig.zeroql.json. By itself it looks like that:

{"$schema":"https://raw.githubusercontent.com/byme8/ZeroQL/main/schema.verified.json","graphql":"./schema.graphql","namespace":"ZeroQL.Client","clientName":"ZeroQLClient"}

Now if you haveZeroQL package installed to yourcsproj, it will automatically detect and execute CLI based on this configuration file on every build. To make sure that it works, the config file should follow the*.zeroql.jsonpattern, or you can add a custom definition in yourcsproj like that:

<ItemGroup>    <ZeroQLConfigInclude="you.custom.config.name.json"/></ItemGroup>

The generated client would be stored inside./obj/ZeroQL folder. So it will never appear in the solution. However, you still have access to generated classes in your source code.

If you want to turn off automatic generation on every build, it is possible to disable it:

<PropertyGroup>   <ZeroQLOnBuildTriggerEnabled>False</ZeroQLOnBuildTriggerEnabled></PropertyGroup>

How to use

Let's suppose that schema.graphql file contains the following:

schema {query:Queriesmutation:Mutation}typeQueries {me:User!user(id:Int!):User}typeMutation {addUser(firstName:String!,lastName:String!):User!addUserProfileImage(userId:Int!file:Upload!):Int!}typeUser {id:Int!firstName:String!lastName:String!role:Role!}typeRole {id:Int!name:String!}

and we want to execute the query like that:

query {me {idfirstNamelastName } }

GraphQL lambda syntax

Here is how we can achieve it with ZeroQL "lambda" syntax:

varhttpClient=newHttpClient();httpClient.BaseAddress=newUri("http://localhost:10000/graphql");varclient=newTestServerGraphQLClient(httpClient);varresponse=awaitclient.Query(o=>o.Me(o=>new{o.Id,o.FirstName,o.LastName}));Console.WriteLine($"GraphQL:{response.Query}");// GraphQL: query { me { id firstName lastName } }Console.WriteLine($"{response.Data.Id}:{response.Data.FirstName}{response.Data.LastName}");// 1: Jon Smith

You can pass arguments inside lambda if needed:

varuserId=1;varresponse=awaitclient.Query(o=>o.User(userId, o=>newUser(o.Id,o.FirstName,o.LastName)));Console.WriteLine($"GraphQL:{response.Query}");// GraphQL: query ($id: Int!) { user(id: $id) { id firstName lastName } }Console.WriteLine($"{response.Data.Id}:{response.Data.FirstName}{response.Data.LastName}");// 1: Jon Smith

There is a limitation for lambda syntax. The variable should be a local variable or a parameter of the function.Otherwise, it will not be included in the lambda closure. As a result, ZeroQL would not be able to get a value.

Here is an example of the function parameter:

publicTask<User>GetUser(intuserId){varresponse=awaitclient.Query(o=>o.User(userId, o=>newUser(o.Id,o.FirstName,o.LastName)));returnresponse.Data;}

To be clear, you don't need actively account for it. ZeroQL will analyze and report errors if something is wrong.

For example, the next sample will not work:

publicintUserId{get;set;}publicTask<User>GetUser(){varresponse=awaitclient.Query(o=>o.User(UserId, o=>newUser(o.Id,o.FirstName,o.LastName)));// ZeroQL will report a compilation error herereturnresponse.Data;}

Also, there is a way to avoid lambda closure:

varvariables=new{Id=1};varresponse=awaitclient.Query(variables,static(i,o)=>o.User(i.Id, o=>newUser(o.Id,o.FirstName,o.LastName)));

You can fetch attached fields:

varvariables=new{Id=1};varresponse=awaitclient.Query(variables,static(i,o)=>o.User(i.Id,            o=>new{o.Id,o.FirstName,o.LastName,Role=o.Role(role=>role.Name)}));Console.WriteLine($"GraphQL:{response.Query}");// GraphQL: query GetUserWithRole($id: Int!) { user(id: $id) { id firstName lastName role { name }  } }Console.WriteLine($"{response.Data.Id}:{response.Data.FirstName}{response.Data.LastName}, Role:{response.Data.Role}");// 1: Jon Smith, Role: Admin

GraphQL request syntax

Request syntax is deprecated because it is not compatible with AOT runtimes and will be removed in future releases.

In more complex queries, the "lambda" syntax may look verbose, and extracting requests into a separate entity would be nice. Now it is possible to do it via the "request" syntax. Here is an example:

// define a requestpublicrecordGetUserQuery(intId):GraphQL<Queries,UserModel?>{publicoverrideUserModel?Execute(Queriesquery)=>query.User(Id, o=>newUserModel(o.Id,o.FirstName,o.LastName));}// execute a requestvarresponse=awaitclient.Execute(newGetUserQuery(variables.FriendId));Console.WriteLine(response.Query);// query GetUserQuery($id: Int!) { user(id: $id) { id firstName lastName } }Console.WriteLine(response.Data);// UserModel { Id = 2, FirstName = Ben, LastName = Smith }

You need to create a record from the base recordGraphQL<TOperationType, TResult>. Where theTOperationType is a root query type(Query,Mutation) that is associated with theGraphQLClient<TQuery, TMutataion> instance.~~

Benchmarks

The complete benchmark source code you can findhere.

The short version looks like this:

[Benchmark]publicasyncTask<string>Raw(){varrawQuery=$$"""        {            "variables": { "id":{{id}} },            "query": "query GetUser($id: Int!){ user(id: $id) { id firstName lastName } }"        }""";varresponse=awaithttpClient.PostAsync("",newStringContent(rawQuery,Encoding.UTF8,"application/json"));varresponseJson=awaitresponse.Content.ReadAsStreamAsync();varqlResponse=JsonSerializer.Deserialize<JsonObject>(responseJson,options);returnqlResponse!["data"]!["user"]!["firstName"]!.GetValue<string>();}[Benchmark]publicasyncTask<string>StrawberryShake(){// query GetUser($id: Int!) {//   user(id: $id) {//       id//       firstName//       lastName//   }// }varfirstname=awaitstrawberryShake.GetUser.ExecuteAsync(id);returnfirstname.Data!.User!.FirstName;}[Benchmark]publicasyncTask<string>ZeroQLLambdaWithoutClosure(){varvariables=new{Id=id};varfirstname=awaitzeroQLClient.Query(variables,static(i,q)=>q.User(i.Id, o=>new{o.Id,o.FirstName,o.LastName}));returnfirstname.Data!.FirstName;}[Benchmark]publicasyncTask<string>ZeroQLLambdaWithClosure(){varid=this.id;varfirstname=awaitzeroQLClient.Query( q=>q.User(id, o=>new{o.Id,o.FirstName,o.LastName}));returnfirstname.Data!.FirstName;}[Benchmark]publicasyncTask<string>ZeroQLRequest(){varfirstname=awaitzeroQLClient.Execute(newGetUserQuery(id));returnfirstname.Data!.FirstName;}// ..publicrecordGetUserQuery(intid):GraphQL<Query,User?>{publicoverrideUser?Execute(Queryquery)=>query.User(id, o=>newUser(o.Id,o.FirstName,o.LastName));}

Here results:

BenchmarkDotNet v0.15.8, macOS Tahoe 26.1 (25B78) [Darwin 25.1.0]Apple M3 Max, 1 CPU, 14 logical and 14 physical cores.NET SDK 10.0.100  [Host]     : .NET 10.0.0 (10.0.0, 10.0.25.52411), Arm64 RyuJIT armv8.0-a  DefaultJob : .NET 10.0.0 (10.0.0, 10.0.25.52411), Arm64 RyuJIT armv8.0-a
MethodMeanErrorStdDevGen0Allocated
Raw62.29 μs0.504 μs0.421 μs0.48835.13 KB
StrawberryShake65.10 μs0.362 μs0.302 μs1.342811.22 KB
ZeroQLLambdaWithoutClosure63.91 μs1.203 μs1.181 μs0.85456.9 KB
ZeroQLLambdaWithClosure65.59 μs1.261 μs1.595 μs0.85457.38 KB
ZeroQLRequest64.49 μs1.244 μs1.163 μs0.73246.48 KB

As you can see, theRaw method is the fastest.TheZeroQL method is a bit faster than theStrawberryShake method.But in absolute terms, all of them are pretty much the same.

So, with theZeroQL, you can forget about the graphql and just use the Linq-like interface.It will have little effect on performance.

Credits

The initial inspiration for this project came from the work done athttps://github.com/Giorgi/GraphQLinq

About

C# GraphQL client with Linq-like syntax

Topics

Resources

License

Stars

Watchers

Forks

Contributors14


[8]ページ先頭

©2009-2026 Movatter.jp