Repository navigation
Releases: charlesdevandiere/graphql-query-builder-dotnet
Release list
3.0.0
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
InvalidOperationExceptioninstead ofStackOverflowException KeyValuePairtype matching —KeyValuePair<string, T>now works for anyT, not justobject- Thread safety —
Build()now creates a freshQueryStringBuilderper call instead of reusing a shared instance DateTimeOffsetcrash — Reflection over an object's properties now skips static ones, soDateTimeOffset(whose staticNow/UtcNowreturn a fresh value on every read) no longer recurses into aStackOverflowException- Indexers and write-only properties — No longer read while serializing an object argument, where they threw
TargetParameterCountException/ArgumentException Guid,DateTimeOffset,TimeSpanandUriarguments — Serialized as strings instead of being exploded into their properties (Guidpreviously produced{})- Accumulating output from a shared builder — A
QueryStringBuilderFactoryreturning 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")withCultureInfo.InvariantCulturein numeric formatting (zero allocations per call) - Extracted shared
PropertyHelperto eliminate duplicated reflection and doubleGetValue()calls
2.2.1
2.2.0
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
Added
nullquery param support- Nullable reference types
2.0.2
2.0.1
Fix:
- The Formatter from Query was not passed to the QueryStringBuilder. #35 Thanks @VictoriaKalinichenko!
2.0.0
Breaking Changes
- Newtonsoft.Json dependency is removed. Use GraphQL.Query.Builder.Formatter.NewtonsoftJson or GraphQL.Query.Builder.Formatter.SystemTextJson instead.
QueryOptions.Formatteris nowFunc<PropertyInfo, string>.QueryFormatters.CamelCaseFormatteris replaced byCamelCasePropertyNameFormatter.Format
Deletions
- Dawn.Guard dependency.
1.6.0
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
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
Added
- DateTime support for query param #27