Seeissues andpull requests done in v4.
GraphQL.NET 4.0 has been highly optimized, typically executing queries at least 50% faster while also providinga 75% memory reduction. Small queries have been measured to run twice as fast as they previously ran. A cachedquery executor is also provided, which can reduce execution time another 20% once the query has been parsed(disabled by default). Variable parsing is also improved to run about 50% faster, and schema build time isnow about 20x faster than previously and requires 1/25th the amount of memory.
See theDocument Caching guide to enabledocument caching.
To facilitate the performance changes, many changes were made to the API that may affect you if you havebuilt custom execution strategies, scalars, parser, or similar core components. Please see the complete listof breaking changes below.
You can now add code toInputObjectGraphType descendants to build an object from the collected argumentfields. The newParseDictionary method is called when variables are being parsed orGetArgument is called,depending on if the argument is stored within variables or as a literal. The method is passed a dictionarycontaining the input object's fields and deserialized values.
By default, forInputObjectGraphType<TSourceType> implementations, the dictionary is passed toObjectExtensions.ToObject in order to convert the dictionary to an object ofTSourceType.You can override the method to have it return an instance of any appropriate type.
Below is a sample which sets a default value for an unsupplied field (this could be done with a defaultvalue set on the field, of course) and converts the name to uppercase:
publicclassHumanInputType:InputObjectGraphType{publicHumanInputType(){ Name="HumanInput";Field<NonNullGraphType<StringGraphType>>("name");Field<StringGraphType>("homePlanet");}publicoverrideobjectParseDictionary(IDictionary<string,object>value){returnnewHuman{ Name=((string)value["name"]).ToUpper(), HomePlanet=value.TryGetValue("homePlanet",outvar homePlanet)?(string)homePlanet:"Unknown", Id=null,};}}Note that pursuant to GraphQL specifications, if a field is optional, not supplied, and has no default,it will not be in the dictionary.
For untypedInputObjectGraphType classes, like shown above, the default behavior ofParseDictionarywill be to return the dictionary.GetArgument<T> will still attempt to convert a dictionary to therequested type viaObjectExtensions.ToObject as it did before.
Custom scalars can now handle serialization or deserialization ofnull values. This can be useful ifyou have a need to coerce certain internal values tonull, such as serializing empty strings tonull.It can also be used to control deserialization of externalnull values, such as deserializingnull tothe value zero.
GraphQL nullability semantics are enforced on the external AST representation of the data. For instance,if a custom scalar converted empty strings tonull during serialization, an error would occur if a fieldresolver tried to return an empty string for a non-null field.
See theCustom Scalarsdocumentation page which describes this in detail.
In v4 we added ability to apply directives to the schema elements and expose user-defined meta-informationvia introspection. This was one of the most requested features not only in GraphQL.NET, but in the entireGraphQL ecosystem as a whole. See theDirectivesdocumentation page which describes the new features in detail.
If you are using theMicrosoft.Extensions.DependencyInjection package, extension methods are provided withintheGraphQL.MicrosoftDI NuGet package for creating a serviceprovider scope during a field resolver's execution. This is useful when accessing a scoped service with a parallelexecution strategy, as typically scoped services are not multi-threaded compatible. The library also provides abuilder to assist constructing a field resolver that relies on scoped services. Below is a sample of a field resolverthat relies on a scoped service and can run concurrently with other field resolvers:
publicclassMyGraphType:ObjectGraphType<Category>{publicMyGraphType(){Field("Name", context=> context.Source.Name);Field<ListGraphType<ProductGraphType>>().Name("Products").Resolve().WithScope().WithService<MyDbContext>().ResolveAsync((context, db)=> db.Products.Where(x=> x.CategoryId== context.Source.Id).ToListAsync());}}SeeDependency Injection for more details.
Introspection results are now sorted based on a configured 'comparer' for a schema. You can configure the comparerby settingISchema.Comparer to an implementation ofISchemaComparer. By default, introspection results arereturned in the order they were defined.
SeeDefault Sort Order of Introspection Query Results below for a sampleof how this can be used to return introspection results that are sorted alphabetically.
When returning lists of information from field resolvers, you can choose to rent an array fromIResolveFieldContext.ArrayPool,populating it with your results and returning the array. The array will be released after the execution completes. This haslimited uses, since the rented array is not guaranteed to be exactly the requested length, so the array would need to bewrapped in order to only return the correct number of entries, triggering a memory allocation (albeit a smaller one):
resolve: context=>{var ints= context.ArrayPool.Rent<int>(1000);// ints.Length >= 1000for(int i=0; i<1000;++i) ints[i]= i;return ints.Constrained(1000);// extension method to return an array or array-like object of a given length});It is not recommended to use this feature for interim calculations, as it is better to work directly withSystem.Buffers.ArrayPool<T>.
GraphQL.GlobalSwitches is a new static class with properties that affect the schema build process:
EnableReadDefaultValueFromAttributes enables or disables setting default values for 'defaultValue' fromDefaultValueAttribute. Enabled by default.EnableReadDeprecationReasonFromAttributes enables or disables setting default values for 'deprecationReason' fromObsoleteAttribute. Enabled by default.EnableReadDescriptionFromAttributes enables or disables setting default values for 'description' fromDescriptionAttribute. Enabled by default.EnableReadDescriptionFromXmlDocumentation enables or disables setting default values for 'description' from XML documentation. Disabled by default.NameValidation configures the validator used when setting theName property on types, arguments, etc. Can be used to disable validationwhen the configuredINameConverter fixes up invalid names. SeeISchema.NameConverter.It is recommended to configure these options once when your application starts, such as within yourvoid Main() method, a staticconstructor of your schema, or a similar location.
Historically, there are two repositories ingraphql-dotnet org that provide APIs for configuringauthorization requirements.
| Name | Package | Description |
|---|---|---|
| server | GraphQL.Server.Authorization.AspNetCore | Integration of GraphQL.NET validation subsystem into ASP.NET Core |
| authorization | GraphQL.Authorization | A toolset for authorizing access to graph types for GraphQL.NET |
Authorization itself is not a specific part of the GraphQL.NET repository, so it was quite natural to keep this functionalityin separate repositories. However, this resulted in some code duplication between repositories. In addition, there was constantconfusion about which of the two projects to use. In v4, we began the process of converging the two projects to a common denominator.Extension methods (seeAuthorizationExtensions) to configure authorization requirements for GraphQL elements (types, fields, schema)were moved to GraphQL.NET repository. These methods will be removed from their respective projects after v4 release.
GraphQL.NET will not receive new dependencies, since all methods just read or write meta information. Calling code changes not required.
2 new methods forScalarGraphType have been added in v4:
public bool CanParseLiteral/CanParseValueThese methods checks for input coercion possibility. They can be overridden for custom scalarsto validate input values without directly getting those values, i.e. without boxing.
New propertyIResolveFieldContext.Parent provides access to the parent context (up to the root).This may be needed to get the parameters of parent nodes.
FieldConfig.ArgumentForISchema.ValueConvertersIParentExecutionNode.ApplyToChildrenExecutionStrategy exposes a number ofprotected virtual methods that can be used to alter the executionof a document without rewriting the entire class. For instance, overridingShouldIncludeNode providesthe ability to control the set of fields that the strategy executes; overridingProcessNodeUnhandledExceptionprovides a way to customize exception handling, and so on.ExecutionContext.ExecutionStrategy andIExecutionStrategy.GetSubFields, theexecution strategy now controls the fields that are returned when requested fromIResolveFieldContext.SubFields.ISchemaNodeVisitorScalars do not coerce values if passed an incompatible type during deserialization from a variable. Previously, values would passthrough theValueConverter while being deserialized. Now theValueConverter is ignored for deserialization of built-in scalars.CallingGetArgument<T> within the field resolver will still call theValueConverter to coerce the input data to the correct type,but if the document is unable to deserialize successfully, the field resolver will not run.
Here are some of the situations that you may run into with version 4:
StringGraphType does not serialize integers to stringsIntGraphType do not deserialize strings to their numeric typeIntGraphType do not coerce floating-point values to their numeric typeBooleanGraphType does not deserialize/serialize strings or integers (e.g. "true", "false", 0, 1, etc)EnumGraphType is stricter, requiring internal values for serialization, and external values for deserializationIdGraphType (which allows any basic type) does not coerce variable values to trimmed strings during deserializationIdGraphType does not trim serialized values (but does convert them to strings)DateTimeGraphType serializes values to strings instead of letting the JSON serializer do soIf you have a schema-first schema, you may run into an issue with enumeration types, since theSchemaBuilder uses the name of eachenumeration value as its value also. In other words, you must return a string corresponding to the enumeration value (e.g,"Cat" or"Dog") rather than a matching C# enumeration value (e.g.Animal.Cat orAnimal.Dog). You can configure theSchemaBuilder tomatch the defined enumeration values to a C# enumeration type in this manner demonstrated below. Then when used as an input type,the values will be parsed into the matching C# enumeration values, and when used as an output type, you must return the C# enumerationvalue (e.g.Animal.Cat) or its underlying value (typically anint). Below are a few examples of how this is configured:
var schema= Schema.For(definitions, c=>{// example 1: define the "Animal" schema enumeration type to use the C# type Animal c.Types.Include<Animal>();// example 2: define the "AnimalType" schema enumeration type to use the C# type Animal c.Types.Include<Animal>("AnimalType");// example 3: define the "Animal" schema enumeration type to use the C# type Animal c.Types.For("Animal").Type=typeof(Animal);});For situations where it is necessary to revert scalars to previous behavior, you can override the built-in scalar by following theinstructions within theCustom Scalars documentation page.
Below is a sample replacement for theBooleanGraphType which will restore the previous behavior exactlyas it was in version 3.x.
publicclassMyBooleanGraphType:BooleanGraphType{publicMyBooleanGraphType(){ Name="Boolean";}publicoverrideobjectParseValue(objectvalue)=>valueswitch{null=>null, _=> ValueConverter.ConvertTo(value,typeof(bool))??ThrowValueConversionException(value)};publicoverrideboolCanParseValue(objectvalue){try{ _=ParseValue(value);returntrue;}catch{returnfalse;}}}// Code-first: add the following line of code to your schema's constructorRegisterType(newMyBooleanGraphType());// Schema-first: add the following line of code after your schema is built, before it is initializedschema.RegisterType(newMyBooleanGraphType());All of the code necessary for a proper custom scalar implementation has been moved from theValueConverter andIAstNodeConverterdirectly into the scalar itself. Some changes will be necessary for custom scalars as follows:
IAstNodeConverter has been completely removed along with the properties relating to it on theSchema. Code that had been configuredfor a custom scalar may need to be moved into the newToAST virtual member of the custom scalar.ValueNode implementations are notrecommended; theToAST member should return one of the base value node types present in the library, such asStringValue orIntValue.If theSerialize method returns a basic .NET type (such asint orstring), the defaultToAST implementation should suffice.
Code withinValueConverter registrations should be moved directly into theParseLiteral and/orParseValue methods. Having aValueConverter registration should no longer be necessary for custom scalars.
ParseLiteral andParseValue must handle null values (NullValue forParseLiteral andnull forParseValue). Typicallythis involves returningnull for each of these cases.
ParseLiteral,ParseValue andSerialize must throw an exception if the value cannot be parsed. Previously, returningnull wouldindicate a conversion failure.ThrowLiteralConversionError,ThrowValueConversionError andThrowSerializationError conveniencemethods are provided for this purpose but any exception is valid to throw.
Serialize's default behavior still callsParseValue. With the other changes, it should be verified if this is still validfor the custom scalar.
IfToAST is overridden, it must process a value ofnull (typically by returning a new instance ofNullValue) and throw anexception if there are serialization errors. TheThrowASTConversionError convenience method is provided for this purpose butany exception is valid to throw.
You may wish to add implementations for the newCanParseValue,CanParseLiteral andIsValidDefault methods. This is not necessaryas the default implementations will callParseValue,ParseLiteral andToAST respectively, returningtrue unless the methodthrows an exception. Adding a custom implementation ofCanParseLiteral can improve performance ifParseLiteral causes memory allocation / boxing.CanParseValue is not used by the framework at this time, andToAST is only called during schema initialization and schema printing.
NameConverter,SchemaFilter andFieldMiddleware have been removed fromExecutionOptions and are now properties onSchema.These properties can be set in the constructor of theSchema instance, or within your DI composition root, or at any time beforeany query is executed. Once a query has been executed, changes to these fields is not allowed, and adding middleware via the field middlewarebuilder has no effect.
IFieldMiddlewareBuilder.Use has been changed to remove the schema from delegate. Since the schema is now known, there is noneed for it to be passed to the middleware builder.Use<T> extension method has been removed. Please use theUse method with a middleware instance instead.SeeField Middleware for more information.
GraphQL.Utilities.ServiceProviderExtensions has been made internal. This affects usages of its extension methodGetRequiredService.Instead, reference theMicrosoft.Extensions.DependencyInjection.Abstractions NuGet package and use the extension method from theMicrosoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions class.
By default fields returned by introspection query are no longer sorted by their names.LegacyV3SchemaComparer can be used to switch to the old behavior.
/// <summary>/// Default schema comparer for GraphQL.NET v3.x.x./// By default only fields are ordered by their names within enclosing type./// </summary>publicsealedclassLegacyV3SchemaComparer:DefaultSchemaComparer{privatestaticreadonlyFieldByNameComparer _instance=newFieldByNameComparer();privatesealedclassFieldByNameComparer:IComparer<IFieldType>{publicintCompare(IFieldType x,IFieldType y)=> x.Name.CompareTo(y.Name);}/// <inheritdoc/>publicoverrideIComparer<IFieldType>FieldComparer(IGraphType parent)=> _instance;}schema.Comparer=newLegacyV3SchemaComparer();IResolveFieldContext Re-useTheIResolveFieldContext instance passed to field resolvers is re-used at the completion of the resolver. Be sure not touse this instance once the resolver finishes executing. To preserve a copy of the context, call.Copy() on the contextto create a copy that is not re-used. Note that it is safe to use the field context within asynchronous field resolvers anddata loaders. Once the asynchronous field resolver or data loader returns its final result, the context will be cleared and may be re-used.Also, any calls to the configuredUnhandledExceptionDelegate will receive a field context copy that will not be re-used,so it is safe to preserve these instances without calling.Copy().
In version 4.7 and newer, the context will not be re-used if the result of the resolver is anIEnumerable, making it safeto return LINQ enumerables that are based on the context source.
The implementation for subscriptions, contained withinSubscriptionExecutionStrategy, has been moved into theGraphQL.SystemReactive NuGet package. The default document executerwill now throw aNotSupportedException when attempting to execute a subscription. Please import the NuGet package and use theSubscriptionDocumentExecuter instead. If you have a custom document executer, you can overrideSelectExecutionStrategy inorder to select theSubscriptionExecutionStrategy instance for subscriptions.
protectedoverrideIExecutionStrategySelectExecutionStrategy(ExecutionContext context){return context.Operation.OperationTypeswitch{ OperationType.Subscription=> SubscriptionExecutionStrategy.Instance, _=>base.SelectExecutionStrategy(context)};}The implementation for data loaders, contained within theGraphQL.DataLoader namespace, has been moved into theGraphQL.DataLoader NuGet package. Please import the NuGetpackage if you use data loaders. No code changes are necessary.
ExecutionOptions.EnableMetrics is disabled by defaultTo enable metrics, please set the option totrue before executing the query.
var result=await schema.ExecuteAsync(options=>{ options.Query="{ hero { id name } }"; options.EnableMetrics=true;});To improve performance, by default GraphQL.NET 4.0 does not pull descriptions for types/fields/etc from XML comments as itdid in 3.x. To re-enable that functionality, seeGlobal Switches above.
IResolveFieldContext.ArgumentsIResolveFieldContext.Arguments now returns anIDictionary<string, ArgumentValue> instead ofIDictionary<string, object> so that itcan be determined if the value returned is a default value or if it is a specified literal or variable.
IResolveFieldContext.HasArgument now returnsfalse whenGetArgument returns a field default value. Note that if a variable is specified,and the variable resolves to its default value, thenHasArgument returnstrue (since the field argument has successfully resolved to a variablespecified by the query).
IProvideMetadata.Metadata is now aDictionary<string, object> instead ofConcurrentDictionary<string, object>, and is not thread safe anymore.If you need to write metadata during execution of field resolvers, lock on the graph type before accessing the dictionary. Do not lock on theMetadata property because there can be concurrency issues accessing the field.
lock(field){intvalue;if(field.Metadata.TryGetValue("counter",outvar valueObject))value=(int)valueObject; field.Metadata["counter"]=value+1;}Strictly speaking, this feature was available before viaGraphTypeTypeRegistry, but it had a significantdrawbacks, since the mapping was static and did not allow registering the same CLR type both as input and output.In v4GraphTypeTypeRegistry was completely removed and theISchema.RegisterTypeMapping(Type, Type)method was added instead (also there are several extension methods).
Consider the following example:
publicclassMoney{publicdecimal Amount{get;set;}publicstring Currency{get;set;}}publicclassAccount{publicMoney Saldo{get;set;}}publicclassMoneyType:ObjectGraphType<Money>{publicMoneyType(){Field(x=> x.Amount);Field(x=> x.Currency);}}publicclassAccountType:ObjectGraphType<Account>{publicMoneyType(){Field(x=> x.Saldo);}}On theField(x => x.Saldo) line when parsing an expression GraphQL.NET should somehow inferthat theMoney CLR type corresponds to theMoneyType GraphType. In fact, this cannot be donewithout specifying additional information from the caller. GraphQL.NET can only infer some primitiveCLR types (int,string,DateTime,Guid, etc.) that match built-in scalars.
Type registration is used for the hint:
GraphTypeTypeRegistry.Register<Money, MoneyType>();// static method before v4schema.RegisterTypeMapping<Money, MoneyType>();// instance method on `ISchema` after v4Note that since v4 it's possible to register both input and output GraphType for the same CLR type.In this case, GraphQL.NET will choose the desired GraphType depending on the context.
publicclassMoney{publicdecimal Amount{get;set;}publicstring Currency{get;set;}}publicclassMoneyType:ObjectGraphType<Money>{publicMoneyType(){Field(x=> x.Amount);Field(x=> x.Currency);}}publicclassMoneyInputType:InputObjectGraphType<Money>{publicMoneyInputType(){Field(x=> x.Amount).Description("Total amount").DefaultValue(100m);Field(x=> x.Currency).DefaultValue("USD");}}schema.RegisterTypeMapping<Money, MoneyType>();schema.RegisterTypeMapping<Money, MoneyInputType>();An alternative way to define the mapping is to use the new properties in theGraphQLMetadata attribute.Consider the following example:
[GraphQLMetadata(InputType=typeof(FilterInputGraphType))]publicclassFilter{publicstring Key{get;set;}publicint Value{get;set;}}publicclassContainerRequest{publicIList<Filter> Filters{get;set;}publicint ClientId{get;set;}publicint AppId{get;set;}}publicclassFilterInputGraphType:InputObjectGraphType<Filter>{publicFilterInputGraphType(){ Name="FilterInput";Field(x=> x.Key);Field(x=> x.Value);}}publicclassMyInputType:InputObjectGraphType<ContainerRequest>{publicMyInputType(){ Name="Input";Field(x=> x.Filters);// when building this field, its type is implicitly inferred to list of FilterInputGraphTypeField(x=> x.ClientId);Field(x=> x.AppId,nullable:true);}}In this case, a call to the registration method is not required, since the schemawill use information from the provided attribute.
Keep in mind that you can register type mappings even for built-in/primitive types if you want to change their behavior:
schema.RegisterTypeMapping<int, MyIntGraphType>();schema.RegisterTypeMapping<string, MySpecialFormattedStringGraphType>();If you have dynamic code that relies on a call toGraphTypeTypeRegistry.Get<T> then you will need to instead utilizea graph type ofGraphQLClrOutputTypeReference<T> orGraphQLClrInputTypeReference<T> whereT is the CLR type.The type reference will be replaced with the proper graph type during schema initialization.
In v4AutoRegisteringObjectGraphType<TSourceType> andAutoRegisteringInputObjectGraphType<TSourceType>classes by default use all properties from the providedTSourceType to generate GraphType's fields (previously theymay skip unmatched properties). If no matching is found for some of the properties, then an exception will be thrownduring schema initialization.
You have multiple options to fix this.
ISchema.RegisterTypeMapping method.excludedProperties parameter of the constructor if you create a type vianew operator.myField.ResolvedType=newAutoRegisteringObjectGraphType<SomeClassWithManyProperties>(x=> x.Address, x=> x.SecretCode);GetRegisteredProperties method.publicclassMyAutoType:AutoRegisteringObjectGraphType<SomeClassWithManyProperties>{protectedoverrideIEnumerable<PropertyInfo>GetRegisteredProperties()=>typeof(SomeClassWithManyProperties).GetProperties(BindingFlags.Public| BindingFlags.Instance).Where(p=> Attribute.IsDefined(p,typeof(ForExportAttribute)));}publicclassMyAutoType:AutoRegisteringObjectGraphType<SomeClassWithManyProperties>{publicMyAutoType():base(x=> x.Address, x=> x.SecretCode){}}IResolveFieldContext.FieldName andIResolveFieldContext.ReturnTypeThese properties have been removed. UseIResolveFieldContext.FieldAst.Name andIResolveFieldContext.FieldDefinition.ResolvedType instead.
GraphQLMetadataAttribute.Type ->GraphQLMetadataAttribute.ResolverTypeThis property was renamed. If you have explicitly set this property in an attribute or used itdirectly anywhere, then just change its name. If you did not explicitly set this property, thedefault continues to beResolverType.Resolver.
DirectiveGraphType.Deprecated,DirectiveGraphType.Include andDirectiveGraphType.Skip static properties were movedinto corresponding virtual properties withinSchemaDirectives class. This is done in order to be able to independently changethe directives implementation without mutual influence on the schemas using them.
GraphQL.Instrumentation.StatsReport and its associated classes have been removed. Please copy the source code intoyour project if you require these classes.LightweightCache.First method has been removed.IGraphType.CollectTypes method has been removed.TypeExtensions.As method has been removedExecutionHelper.SubFieldsFor andExecutionHelper.DoesFragmentConditionMatch methods have been removed.ExecutionHelper.GetVariableValue has been removed, and the signature forExecutionHelper.CoerceValue has changed.ExecutionHelper exceptCoerceValue andGetArgumentValues have been moved into protectedmethods withinExecutionStrategy. Method signatures may have changed a very little.RootExecutionNode's constructor requires the root field's selection set, butnull can be provided if this valueis not needed by the execution strategy.UnhandledExceptionContext.Context is now of typeIExecutionContext, returning a read-only view of the execution context.NodeExtensions,AstNodeExtensions classes have been removed.CoreToVanillaConverter class becamestatic and most of its members have been removed.GraphQL.Language.AST.Field.MergeSelectionSet method has been removed.CoreToVanillaConverter.Convert method now requires only oneGraphQLDocument argument.GraphTypesLookup has been renamed toSchemaTypes with a significant decrease in public APIsGraphTypesLookup.Create has been removed; use theSchemaTypes constructor instead.TypeCollectionContext class is now internal, also all methods with this parameter inGraphTypesLookup (nowSchemaTypes) are private.GraphTypesLookup.ApplyTypeReferences is now private.IHaveDefaultValue.Type has been moved toIProvideResolvedType.TypeErrorLocation struct becamereadonly.DebugNodeVisitor class has been removed.OverlappingFieldsCanBeMerged are now private.EnumerableExtensions.Apply for dictionaries has been removed.ISubscriptionExecuter has been removed.EnterLeaveListener has been removed and the signatures ofINodeVisitor.Enter andINodeVisitor.Leave havechanged.NodeVisitors class has been added in its place.TypeInfo.GetAncestors() has been changed toTypeInfo.GetAncestor(int index)StringUtils have been removed; please use extension methods withinStringExtensions instead.ISchema.FindDirective,ISchema.RegisterDirective,ISchema.RegisterDirectives methods were moved intoSchemaDirectives classISchema.FindType method was moved intoSchemaTypes[string typeName] indexerISchemaNodeVisitor methods have been changes to better support schema traversalSourceLocation,NameNode andBasicVisitor have been changed to areadonly struct.ObjectExtensions.GetInterface has been removed along with two overloads ofGetPropertyValue.void INode.Visit<TState>(System.Action<INode, TState> action, TState state) method has been added.IEnumerable<T> properties on schema and graph types have been changed to custom collections:SchemaDirectives,SchemaTypes,TypeFields,PossibleTypes,Interfaces andResolvedInterfacesINode.IsEqualTo and related methods have been removed.ApolloTracing.ConvertTime is now private andResolverTrace.Path does not initialize an empty list when created.SchemaBuilder.RegisterType andSchemaBuilder.RegisterTypes methods have been removed, useISchema.RegisterType on the builded schema instead.SchemaBuilder.Directives andSchemaBuilder.RegisterDirectiveVisitor have been removed, useISchema.RegisterVisitor on the builded schema instead.SchemaPrinter.IsBuiltInScalar,SchemaPrinter.IsSpecDirective,SchemaPrinter.IsIntrospectionType,SchemaPrinter.IsDefinedType methods have been removed from public APISchemaPrinterOptions.CustomScalars property has been removedValidationContext.Print(INode node) andValidationContext.Print(IGraphType type) methods have been removedDirectives.HasDuplicates property has been removedKnownDirectives validation rule has been renamed toKnownDirectivesInAllowedLocations and now also generates5.7.2 validation error numberAstPrinter supporting classes have been removed; the static methodAstPrinter.Print(INode node) is the only exposed member.Language.AST.Fields was replaced withDictionary<string, Field>IResolveFieldContext.Fragments was removed; useIResolveFieldContext.Document.Fragments insteadExecutionContext.Fragments was removed; useExecutionContext.Document.Fragments insteadAbstractGraphTypeExtensions.GetTypeOf was removed; useAbstractGraphTypeExtensions.GetObjectType insteadTypeConfig.FieldFor(string, IServiceProvider) andTypeConfig.SubscriptionFieldFor(string, IServiceProvider)methods were merged into singleTypeConfig.FieldFor(string) method and just return the required configuration without its initializationSystem.Memory APIs.StartTime andEndTime are properly serialized as UTC values.Connection<TNode, TEdge>.TotalCount has been changed from anint to anint?. This allows for returningnull indicating that the total count is unknown.InputObjectGraphType.ParseDictionary has been added so that customized deserialization behavior can be specified for input objects (aka input resolvers).IfInputObjectGraphType<T> is used, andGetArgument<T> is called with the same type, no behavior changes will occur by default.IfInputObjectGraphType<T> is used, butGetArgument<T> is called with a different type, coercion may fail. OverrideParseDictionaryto force resolving the input object to the correct type. SeeInput Object Custom Deserializers above.ExecutionResult.Data format breaking changes.BothGraphQL.NewtonsoftJson andGraphQL.SystemTextJson serializers received the necessary changes to produce the same JSON as before.However, consumers usingExecutionResult instances directly most likely will not work correctly.Call((ExecutionNode)result.Data).ToValue() to return the data in the same format as 3.x (as a dictionary).ExecutionStrategy methods are nowprotectedObjectExecutionNode.SubFields property type was changed fromDictionary<string, ExecutionNode> toExecutionNode[]ExecutionNode.IsResultSet has been removedExecutionNode.Source is read-only; additional derived classes have been added for subscriptionsNameValidator.ValidateName accepts an enum instead of a string for its second argumentNameValidator.ValidateNameOnSchemaInitialize has been made internal andValidationOnSchemaInitialize has been removedExecutionNode.PropagateNull must be called beforeExecutionNode.ToValue; see reference implementationIDocumentValidator.ValidateAsync does not takeoriginalQuery parameter; useDocument.OriginalQuery insteadIDocumentValidator.ValidateAsync now returns(IValidationResult validationResult, Variables variables) tuple instead of singleIValidationResult beforeGraphQLExtensions.IsValidLiteralValue now returnsstring instead ofstring[] and is a member ofValidationContext.