Skip to content

Releases: charlesdevandiere/graphql-query-builder-dotnet

3.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Aug 08:25
41f2780

Breaking Changes

Renamed core types

Before After
Query<T> GraphQLField<T>
IQuery<T> IGraphQLField<T>
IQuery IGraphQLField

Null properties included by default

AddArguments<T>() and object serialization now include null properties. The previous behavior silently dropped them.

Set QueryOptions.DefaultIgnoreCondition = QueryIgnoreCondition.WhenWritingNull to restore the old behavior.

Duplicate argument keys overwrite

AddArgument now overwrites the value when the same key is added twice, instead of throwing ArgumentException.

Names validated

Every identifier the builder emits is validated against the GraphQL name pattern [a-zA-Z_][a-zA-Z0-9_]*:
field names, aliases, argument keys, union type names, operation names, directive names and argument
names, fragment names and type names, and variable names. Variable types are validated as GraphQL type
references (ID!, [String!]!).

Stricter string escaping

Strings in argument values now escape \, \n, \r, and \t in addition to ".

New Features

Operation wrapper

Build complete GraphQL operations with type and name using GraphQLOperation:

new GraphQLOperation(OperationType.Query, "GetUser")
    .AddQuery(new GraphQLField<User>("user").AddArgument("id", 1).AddField(u => u.Name))
    .Build();
// => "query GetUser{user(id:1){Name}}"

Supports Query, Mutation, Subscription, and shorthand (no type keyword).

Variables

Define typed variables and reference them in arguments:

var idVar = new GraphQLVariable("id", "ID!");

new GraphQLOperation(OperationType.Query)
    .AddVariable(idVar)
    .AddQuery(new GraphQLField<User>("user").AddArgument("id", idVar.Reference()).AddField(u => u.Name))
    .Build();
// => "query($id:ID!){user(id:$id){Name}}"

Supports default values: new GraphQLVariable("limit", "Int", 10).

Directives

Attach @include, @skip, or custom directives to fields:

var showVar = new GraphQLVariable("show", "Boolean!");

new GraphQLField<User>("user")
    .AddField(u => u.Name, GraphQLDirective.Include(showVar.Reference()))
    .AddField(u => u.Age, GraphQLDirective.Skip(showVar.Reference()));

Custom directives with arguments:

new GraphQLDirective("cacheControl", new Dictionary<string, object?> { { "maxAge", 300 } })

Fragments

Define reusable field selections and spread them across queries:

var fields = new GraphQLFragment("UserFields", "User",
    new GraphQLField<User>("User").AddField(u => u.Name).AddField(u => u.Email));

new GraphQLOperation(OperationType.Query)
    .AddQuery(new GraphQLField<User>("user").AddFragment(fields))
    .AddFragment(fields)
    .Build();
// => "query{user{...UserFields}} fragment UserFields on User{Name Email}"

Fragment definitions are deduplicated by name.

Multiple root fields

Compose multiple fields in a single operation:

new GraphQLOperation(OperationType.Query)
    .AddQuery(new GraphQLField<User>("user").AddArgument("id", 1).AddField(u => u.Name))
    .AddQuery(new GraphQLField<Post>("posts").AddField(p => p.Title))
    .Build();
// => "query{user(id:1){Name} posts{Title}}"

Null property handling options

New QueryOptions.DefaultIgnoreCondition property with three modes:

Value Behavior
Never (default) Include all properties
WhenWritingNull Skip null properties
WhenWritingDefault Skip null, 0, false, and other type defaults

Bug Fixes

  • GraphQL injection via string arguments — Strings now properly escape backslashes, newlines, carriage returns, and tabs
  • Circular reference crash — Objects with circular references now throw InvalidOperationException instead of StackOverflowException
  • KeyValuePair type matching — KeyValuePair<string, T> now works for any T, not just object
  • Thread safety — Build() now creates a fresh QueryStringBuilder per call instead of reusing a shared instance
  • DateTimeOffset crash — Reflection over an object's properties now skips static ones, so DateTimeOffset (whose static Now/UtcNow return a fresh value on every read) no longer recurses into a StackOverflowException
  • Indexers and write-only properties — No longer read while serializing an object argument, where they threw TargetParameterCountException / ArgumentException
  • Guid, DateTimeOffset, TimeSpan and Uri arguments — Serialized as strings instead of being exploded into their properties (Guid previously produced {})
  • Accumulating output from a shared builder — A QueryStringBuilderFactory returning the same instance no longer appends each build onto the previous one; every build, including nested sub-queries, gets its own buffer

Performance

  • Replaced CultureInfo.CreateSpecificCulture("en-us") with CultureInfo.InvariantCulture in numeric formatting (zero allocations per call)
  • Extracted shared PropertyHelper to eliminate duplicated reflection and double GetValue() calls

2.2.1

Choose a tag to compare

@charlesdevandiere charlesdevandiere released this 28 Oct 15:15
4709ec6
update System.Text.Json (#46)

2.2.0

Choose a tag to compare

@charlesdevandiere charlesdevandiere released this 25 Jul 15:12
330a214

Added

  • Union support with:
    • IQuery<TSource> AddUnion<TUnionType>(string typeName, Func<IQuery<TUnionType>, IQuery<TUnionType>> build)
    • IQuery<TSource> AddUnion<TUnionType>(Func<IQuery<TUnionType>, IQuery<TUnionType>> build)

2.1.0

Choose a tag to compare

@charlesdevandiere charlesdevandiere released this 22 Jul 07:44
3759302

Added

  • null query param support
  • Nullable reference types

2.0.2

Choose a tag to compare

@charlesdevandiere charlesdevandiere released this 24 Aug 08:45
fd6ca38
update sample dependencies (#40)

2.0.1

Choose a tag to compare

@charlesdevandiere charlesdevandiere released this 05 Sep 07:15
1fe99c2

Fix:

2.0.0

Choose a tag to compare

@charlesdevandiere charlesdevandiere released this 21 Apr 11:24
481cc90

Breaking Changes

Deletions

  • Dawn.Guard dependency.

1.6.0

Choose a tag to compare

@charlesdevandiere charlesdevandiere released this 04 Apr 08:29
13f530e

Added

String arguments are now encoded:

string json = "{ \"foo\": \"bar\" }";
var query = new Query<object>("foo")
    .AddArgument("json", json)
    .AddField("foo")
    .Build();
// foo(json:"{ \"foo\": \"bar\" }"){foo}

1.5.0

Choose a tag to compare

@charlesdevandiere charlesdevandiere released this 01 Apr 08:43
e9516f2

Added

Query<TSource>.AddArguments<TArguments>(TArguments arguments) method now supports inner object:

var query = new Query<Truck>("truck")
    .AddArguments(new {
        id = "yk8h4vn0",
        km = 2100,
        imported = true,
        page = new { from = 1, to = 100 }
    });

Thanks @carloshenriquecarniatto!

1.4.0

Choose a tag to compare

@charlesdevandiere charlesdevandiere released this 31 Mar 04:55
32e4b78

Added

  • DateTime support for query param #27