{"id":13591582,"url":"https://github.com/byme8/ZeroQL","last_synced_at":"2025-04-08T17:32:01.550Z","repository":{"id":44391989,"uuid":"507999723","full_name":"byme8/ZeroQL","owner":"byme8","description":"C# GraphQL client with Linq-like syntax","archived":false,"fork":false,"pushed_at":"2024-05-19T18:47:36.000Z","size":952,"stargazers_count":246,"open_issues_count":6,"forks_count":14,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-05-20T20:45:13.136Z","etag":null,"topics":["client","csharp","csharp-sourcegenerator","dotnet","graphql"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/byme8.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-06-27T17:20:51.000Z","updated_at":"2024-05-30T08:22:18.445Z","dependencies_parsed_at":"2023-11-18T18:22:27.618Z","dependency_job_id":"7fd331d2-3dbe-426a-b0d0-68d6df882c60","html_url":"https://github.com/byme8/ZeroQL","commit_stats":{"total_commits":233,"total_committers":6,"mean_commits":"38.833333333333336","dds":0.4721030042918455,"last_synced_commit":"f48aa0f01c85b5d27d66fff147430649ed1a9a5b"},"previous_names":["byme8/linqql"],"tags_count":110,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byme8%2FZeroQL","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byme8%2FZeroQL/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byme8%2FZeroQL/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byme8%2FZeroQL/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/byme8","download_url":"https://codeload.github.com/byme8/ZeroQL/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247892569,"owners_count":21013737,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","csharp","csharp-sourcegenerator","dotnet","graphql"],"created_at":"2024-08-01T16:00:59.457Z","updated_at":"2025-04-08T17:31:56.540Z","avatar_url":"https://github.com/byme8.png","language":"C#","readme":"# ZeroQL | [![GitHub](https://img.shields.io/github/license/byme8/ZeroQL?color=blue\u0026style=flat-square)](https://github.com/byme8/ZeroQL/blob/main/LICENCE) [![Nuget](https://img.shields.io/nuget/v/zeroql?color=blue\u0026style=flat-square)](https://www.nuget.org/packages/ZeroQL) [![.NET](https://github.com/byme8/ZeroQL/actions/workflows/dotnet.yml/badge.svg)](https://github.com/byme8/ZeroQL/actions/workflows/dotnet.yml)\n\n🚀 Welcome to ZeroQL, a high-performance C#-friendly GraphQL client! 🎉\n\nZeroQL 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.\n\n# Features\n\nHere's a quick rundown of what ZeroQL can do at the moment:\n- [x] Bootstrap schema.graphql file from graphql endpoint\n- [x] Bootstrap client from schema.graphql file\n- [x] Support for queries and mutations\n    - [x] [\"Lambda\" like syntax](#graphql-lambda-syntax)\n    - [x] [\"Request\" like syntax](#graphql-request-syntax)\n- [ ] Support for subscriptions\n- [x] [Support for fragments](https://github.com/byme8/ZeroQL/wiki/Fragments)\n- [x] [Support for interfaces](https://github.com/byme8/ZeroQL/wiki/Interfaces)\n- [x] [Support for unions](https://github.com/byme8/ZeroQL/wiki/Unions)\n- [x] [Support for scalars](https://github.com/byme8/ZeroQL/wiki/User-scalars)\n- [x] [Support for file uploads](https://github.com/byme8/ZeroQL/wiki/File-upload)\n- [x] [Support for persisted queries](https://github.com/byme8/ZeroQL/wiki/Persisted-queries)\n- [ ] Support for @defer\n- [ ] Support for @stream\n\nYou can find the full wiki [here](https://github.com/byme8/ZeroQL/wiki) or just by clicking on the feature bullet point you are interested in.\n\nCheck out our articles to learn more about ZeroQL:\n- [ZeroQL - C# friendly graphql client](https://dev.to/byme8/zeroql-c-friendly-graphql-4134)\n- [ZeroQL - C# GraphQL client adds fragments support](https://dev.to/byme8/zeroql-c-graphql-client-adds-fragments-support-1lcf)\n- [ZeroQL V2](https://dev.to/byme8/zeroql-v2-c-graphql-client-1o8d)\n- [ZeroQL V3](https://dev.to/byme8/zeroql-v3-c-friendly-graphql-client-4b8n)\n- [ZeroQL V6](https://dev.to/byme8/zeroql-linq-graphql-client-updates-2akn)\n\n# How to setup\n\n\u003e Here, you can find the setup for net6.0+ projects. \n\u003e You can find [netstandard or .Net Framework](https://github.com/byme8/ZeroQL/wiki/netstandard-setup) and [Unity](https://github.com/byme8/ZeroQL/wiki/Unity-setup) setup in [wiki](https://github.com/byme8/ZeroQL/wiki).\n\nThe initial setup:\n``` bash\n# create console app\ndotnet new console -o QLClient\n# go to project folder \ncd QLClient\n# create manifest file to track nuget tools\ndotnet new tool-manifest \n# add ZeroQL.CLI nuget tool\ndotnet tool install ZeroQL.CLI # or 'dotnet tool restore' once you pulled the existing repository\n# add ZeroQL nuget package\ndotnet add package ZeroQL \n# fetch graphql schema from server(creates schema.graphql file)\ndotnet zeroql schema pull --url http://localhost:10000/graphql\n# to create ZeroQL config file: ./config.zeroql.json\ndotnet zeroql config init\n# build the project to initiate the ZeroQL client generation with options specified inside config.zeroql.json\ndotnet build\n```\n\nThe build should be successful, and now we can use the generated client.\n\n## Config\n\nThe command `` dotnet zeroql config init `` creates the `` config.zeroql.json ``. By itself it looks like that:\n``` json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/byme8/ZeroQL/main/schema.verified.json\",\n  \"graphql\": \"./schema.graphql\",\n  \"namespace\": \"ZeroQL.Client\",\n  \"clientName\": \"ZeroQLClient\"\n}\n```\nNow if you have `` ZeroQL `` package installed to your `` csproj ``, 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.json ``pattern, or you can add a custom definition in your `` csproj `` like that:\n``` xml\n\u003cItemGroup\u003e\n    \u003cZeroQLConfig Include=\"you.custom.config.name.json\"/\u003e\n\u003c/ItemGroup\u003e\n```\nThe 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.\n\nIf you want to turn off automatic generation on every build, it is possible to disable it:\n``` xml\n\u003cPropertyGroup\u003e\n   \u003cZeroQLOnBuildTriggerEnabled\u003eFalse\u003c/ZeroQLOnBuildTriggerEnabled\u003e\n\u003c/PropertyGroup\u003e\n```\n\n# How to use\n\nLet's suppose that schema.graphql file contains the following:\n``` graphql\nschema {\n  query: Queries\n  mutation: Mutation\n}\n\ntype Queries {\n  me: User!\n  user(id: Int!): User\n}\n\ntype Mutation {\n  addUser(firstName: String!, lastName: String!): User!\n  addUserProfileImage(userId: Int! file: Upload!): Int!\n}\n\ntype User {\n  id: Int!\n  firstName: String!\n  lastName: String!\n  role: Role!\n}\n\ntype Role {\n  id: Int!\n  name: String!\n}\n```\n\nand we want to execute the query like that:\n``` graphql\nquery { me { id firstName lastName } }\n```\n\n## GraphQL lambda syntax\n\nHere is how we can achieve it with ZeroQL \"lambda\" syntax:\n``` csharp\nvar httpClient = new HttpClient();\nhttpClient.BaseAddress = new Uri(\"http://localhost:10000/graphql\");\n\nvar client = new TestServerGraphQLClient(httpClient);\n\nvar response = await client.Query(o =\u003e o.Me(o =\u003e new { o.Id, o.FirstName, o.LastName }));\n\nConsole.WriteLine($\"GraphQL: {response.Query}\"); // GraphQL: query { me { id firstName lastName } }\nConsole.WriteLine($\"{response.Data.Id}: {response.Data.FirstName} {response.Data.LastName}\"); // 1: Jon Smith\n```\n\nYou can pass arguments inside lambda if needed:\n``` csharp\nvar userId = 1;\nvar response = await client.Query(o =\u003e o.User(userId, o =\u003e new User(o.Id, o.FirstName, o.LastName)));\n\nConsole.WriteLine($\"GraphQL: {response.Query}\"); // GraphQL: query ($id: Int!) { user(id: $id) { id firstName lastName } }\nConsole.WriteLine($\"{response.Data.Id}: {response.Data.FirstName} {response.Data.LastName}\"); // 1: Jon Smith\n```\n\nThere is a limitation for lambda syntax. The variable should be a local variable or a parameter of the function.\nOtherwise, it will not be included in the lambda closure. As a result, ZeroQL would not be able to get a value.\n\nHere is an example of the function parameter:\n``` csharp\npublic Task\u003cUser\u003e GetUser(int userId)\n{\n    var response = await client.Query(o =\u003e o.User(userId, o =\u003e new User(o.Id, o.FirstName, o.LastName)));\n    return response.Data;\n}\n```\nTo be clear, you don't need actively account for it. ZeroQL will analyze and report errors if something is wrong.\n\nFor example, the next sample will not work:\n``` csharp\n\npublic int UserId { get; set; }\n\npublic Task\u003cUser\u003e GetUser()\n{\n    var response = await client.Query(o =\u003e o.User(UserId, o =\u003e new User(o.Id, o.FirstName, o.LastName))); // ZeroQL will report a compilation error here\n    return response.Data;\n}\n```\n\nAlso, there is a way to avoid lambda closure:\n``` csharp\nvar variables = new { Id = 1 };\nvar response = await client.Query(variables, static (i, o) =\u003e o.User(i.Id, o =\u003e new User(o.Id, o.FirstName, o.LastName)));\n```\n\nYou can fetch attached fields:\n``` csharp\nvar variables = new { Id = 1 };\nvar response = await client.Query(\n    variables,\n    static (i, o) =\u003e o\n        .User(i.Id,\n            o =\u003e new\n            {\n                o.Id,\n                o.FirstName,\n                o.LastName,\n                Role = o.Role(role =\u003e role.Name)\n            }));\n\nConsole.WriteLine($\"GraphQL: {response.Query}\"); // GraphQL: query GetUserWithRole($id: Int!) { user(id: $id) { id firstName lastName role { name }  } }\nConsole.WriteLine($\"{response.Data.Id}: {response.Data.FirstName} {response.Data.LastName}, Role: {response.Data.Role}\"); // 1: Jon Smith, Role: Admin\n```\n\n## GraphQL request syntax\n\nIn 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:\n``` csharp\n\n// define a request\npublic record GetUserQuery(int Id) : GraphQL\u003cQueries, UserModel?\u003e\n{\n    public override UserModel? Execute(Queries query) \n        =\u003e query.User(Id, o =\u003e new UserModel(o.Id, o.FirstName, o.LastName));\n}\n\n// execute a request\nvar response = await client.Execute(new GetUserQuery(variables.FriendId));\n\nConsole.WriteLine(response.Query); // query GetUserQuery($id: Int!) { user(id: $id) { id firstName lastName } }\nConsole.WriteLine(response.Data); // UserModel { Id = 2, FirstName = Ben, LastName = Smith }\n\n```\n\nYou need to create a record from the base record `` GraphQL\u003cTOperationType, TResult\u003e ``. Where the `` TOperationType `` is a root query type(`` Query ``, `` Mutation ``) that is associated with the `` GraphQLClient\u003cTQuery, TMutataion\u003e `` instance.\n\n# Benchmarks\n\nThe complete benchmark source code you can find [here](https://github.com/byme8/ZeroQL/blob/main/src/Benchmarks/ZeroQL.Benchmark/RawVsZeroQLBenchmark.cs).\n\nThe short version looks like this:\n``` csharp\n[Benchmark]\npublic async Task\u003cstring\u003e Raw()\n{\n    var rawQuery = \n        $$\"\"\"\n        {\n            \"variables\": { \"id\": {{id}} }, \n            \"query\": \"query GetUser($id: Int!){ user(id: $id) { id firstName lastName } }\" \n        }\n        \"\"\";\n    var response = await httpClient.PostAsync(\"\", new StringContent(rawQuery, Encoding.UTF8, \"application/json\"));\n    var responseJson = await response.Content.ReadAsStreamAsync();\n    var qlResponse = JsonSerializer.Deserialize\u003cJsonObject\u003e(responseJson, options);\n\n    return qlResponse![\"data\"]![\"user\"]![\"firstName\"]!.GetValue\u003cstring\u003e();\n}\n\n[Benchmark]\npublic async Task\u003cstring\u003e StrawberryShake()\n{\n    // query GetUser($id: Int!) {\n    //   user(id: $id) {\n    //       id\n    //       firstName\n    //       lastName\n    //   }\n    // }\n    var firstname = await strawberryShake.GetUser.ExecuteAsync(id);\n    return firstname.Data!.User!.FirstName;\n}\n\n[Benchmark]\npublic async Task\u003cstring\u003e ZeroQLLambdaWithoutClosure()\n{\n    var variables = new { Id = id };\n    var firstname = await zeroQLClient.Query(\n        variables, static (i, q)\n            =\u003e q.User(i.Id, o =\u003e new { o.Id, o.FirstName, o.LastName }));\n\n    return firstname.Data!.FirstName;\n}\n\n[Benchmark]\npublic async Task\u003cstring\u003e ZeroQLLambdaWithClosure()\n{\n    var id  = this.id;\n    var firstname = await zeroQLClient.Query( q\n            =\u003e q.User(id, o =\u003e new { o.Id, o.FirstName, o.LastName }));\n\n    return firstname.Data!.FirstName;\n}\n\n[Benchmark]\npublic async Task\u003cstring\u003e ZeroQLRequest()\n{\n    var firstname = await zeroQLClient.Execute(new GetUserQuery(id));\n\n    return firstname.Data!.FirstName;\n}\n\n// ..\npublic record GetUserQuery(int id) : GraphQL\u003cQuery, User?\u003e\n{\n    public override User? Execute(Query query)\n        =\u003e query.User(id, o =\u003e new User(o.Id, o.FirstName, o.LastName));\n}\n```\n\nHere results:\n``` ini\n\nBenchmarkDotNet=v0.13.2, OS=macOS 14.5 (23F79) [Darwin 23.5.0]\nApple M3 Max, 1 CPU, 14 logical and 14 physical cores\n.NET SDK=8.0.301\n  [Host]     : .NET 8.0.6 (8.0.624.26715), Arm64 RyuJIT AdvSIMD\n  DefaultJob : .NET 8.0.6 (8.0.624.26715), Arm64 RyuJIT AdvSIMD\n\n\n```\n|                     Method |     Mean |    Error |   StdDev |   Gen0 | Allocated |\n|--------------------------- |---------:|---------:|---------:|-------:|----------:|\n|                        Raw | 68.65 μs | 0.277 μs | 0.231 μs | 0.6104 |   5.34 KB |\n|            StrawberryShake | 73.48 μs | 0.362 μs | 0.321 μs | 1.3428 |  11.58 KB |\n| ZeroQLLambdaWithoutClosure | 69.55 μs | 0.376 μs | 0.351 μs | 0.7324 |   6.74 KB |\n|    ZeroQLLambdaWithClosure | 70.43 μs | 0.439 μs | 0.410 μs | 0.8545 |   7.22 KB |\n|              ZeroQLRequest | 69.95 μs | 0.439 μs | 0.366 μs | 0.7324 |   6.32 KB |\n\nAs you can see, the ``Raw`` method is the fastest.\nThe ``ZeroQL`` method is a bit faster than the ``StrawberryShake`` method. \nBut in absolute terms, all of them are pretty much the same.\n\nSo, with the `` ZeroQL ``, you can forget about the graphql and just use the Linq-like interface. \nIt will have little effect on performance.\n\n# Credits\n\nThe initial inspiration for this project came from the work done at https://github.com/Giorgi/GraphQLinq\n","funding_links":[],"categories":["C\\#","GraphQL","C#","Implementations"],"sub_categories":[".NET"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbyme8%2FZeroQL","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbyme8%2FZeroQL","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbyme8%2FZeroQL/lists"}