From dbbab4e486c7c6e328b0f8cfac748c2465176d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?DESKTOP-INNBGMR=5C=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=B4=D1=80?= Date: Tue, 20 May 2025 17:21:19 +0300 Subject: [PATCH 1/3] PropertySetTrackingDto, Arguments as Dictinary --- .../Compiler/EntityGraphQLQueryWalker.cs | 18 ++--- .../EntityQuery/Grammar/IdentityExpression.cs | 2 +- .../Compiler/GqlNodes/BaseGraphQLField.cs | 6 +- .../GqlNodes/BaseGraphQLQueryField.cs | 2 +- .../Compiler/GqlNodes/CompileContext.cs | 2 +- .../GqlNodes/ExecutableGraphQLStatement.cs | 4 +- .../Compiler/GqlNodes/GraphQLDirective.cs | 8 +-- .../Compiler/GqlNodes/GraphQLDocument.cs | 4 +- .../GqlNodes/GraphQLFragmentStatement.cs | 4 +- .../GqlNodes/GraphQLListSelectionField.cs | 2 +- .../Compiler/GqlNodes/GraphQLMutationField.cs | 2 +- .../GqlNodes/GraphQLObjectProjectionField.cs | 2 +- .../Compiler/GqlNodes/GraphQLScalarField.cs | 2 +- .../GqlNodes/GraphQLSubscriptionField.cs | 2 +- .../Compiler/GqlNodes/IGraphQLNode.cs | 2 +- .../Compiler/Util/ArgumentUtil.cs | 8 ++- .../Extensions/DictionaryExtensions.cs | 11 +++ src/EntityGraphQL/Schema/BaseField.cs | 2 +- src/EntityGraphQL/Schema/Field.cs | 4 +- src/EntityGraphQL/Schema/IField.cs | 2 +- src/EntityGraphQL/Schema/MethodField.cs | 23 ++++++- .../IPropertySetTrackingDto.cs | 9 +++ .../PropertyTracker/PropertySetTrackingDto.cs | 20 ++++++ src/examples/demo/Mutations/DemoMutations.cs | 68 ++++++++++++++++++- src/examples/demo/schema.graphql | 4 +- 25 files changed, 171 insertions(+), 42 deletions(-) create mode 100644 src/EntityGraphQL/Schema/PropertyTracker/IPropertySetTrackingDto.cs create mode 100644 src/EntityGraphQL/Schema/PropertyTracker/PropertySetTrackingDto.cs diff --git a/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs b/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs index ed13497d..6a9464a9 100644 --- a/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs +++ b/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs @@ -132,7 +132,7 @@ private Dictionary ProcessVariableDefinitions(OperationDefiniti var directives = ProcessFieldDirectives(ExecutableDirectiveLocation.VARIABLE_DEFINITION, item.Directives); foreach (var directive in directives) { - directive.VisitNode(ExecutableDirectiveLocation.VARIABLE_DEFINITION, schemaProvider, null, new Dictionary(), null, null); + directive.VisitNode(ExecutableDirectiveLocation.VARIABLE_DEFINITION, schemaProvider, null, new Dictionary(), null, null); } } @@ -282,7 +282,7 @@ protected override void VisitField(FieldNode node, IGraphQLNode? context) } } - public BaseGraphQLQueryField ParseFieldSelect(Expression fieldExp, IField fieldContext, string name, IGraphQLNode context, SelectionSetNode selection, Dictionary? arguments) + public BaseGraphQLQueryField ParseFieldSelect(Expression fieldExp, IField fieldContext, string name, IGraphQLNode context, SelectionSetNode selection, Dictionary? arguments) { if (fieldContext.ReturnType.IsList) { @@ -317,7 +317,7 @@ private GraphQLListSelectionField BuildDynamicSelectOnCollection( string resultName, IGraphQLNode context, SelectionSetNode selection, - Dictionary? arguments + Dictionary? arguments ) { if (context == null) @@ -346,7 +346,7 @@ private GraphQLObjectProjectionField BuildDynamicSelectForObjectGraph( IGraphQLNode context, string name, SelectionSetNode selection, - Dictionary? arguments + Dictionary? arguments ) { if (context == null) @@ -362,9 +362,9 @@ private GraphQLObjectProjectionField BuildDynamicSelectForObjectGraph( return graphQLNode; } - private Dictionary ProcessArguments(IField field, IEnumerable queryArguments) + private Dictionary ProcessArguments(IField field, IEnumerable queryArguments) { - var args = new Dictionary(); + var args = new Dictionary(); foreach (var arg in queryArguments) { var argName = arg.Name.Value; @@ -373,7 +373,7 @@ private Dictionary ProcessArguments(IField field, IEnumerable ProcessFieldDirectives(ExecutableDirectiveLocatio if (!processor.Location.Contains(location)) throw new EntityGraphQLCompilerException($"Directive '{directive.Name.Value}' can not be used on '{location}'"); var argTypes = processor.GetArguments(schemaProvider); - var args = new Dictionary(); + var args = new Dictionary(); foreach (var arg in directive.Arguments) { var argVal = ProcessArgumentOrVariable(arg.Name.Value, schemaProvider, arg, argTypes[arg.Name.Value].RawType); @@ -441,7 +441,7 @@ protected override void VisitFragmentDefinition(FragmentDefinitionNode node, IGr { foreach (var directive in ProcessFieldDirectives(ExecutableDirectiveLocation.FRAGMENT_DEFINITION, node.Directives)) { - directive.VisitNode(ExecutableDirectiveLocation.FRAGMENT_DEFINITION, schemaProvider, fragDef, new Dictionary(), null, null); + directive.VisitNode(ExecutableDirectiveLocation.FRAGMENT_DEFINITION, schemaProvider, fragDef, new Dictionary(), null, null); } } diff --git a/src/EntityGraphQL/Compiler/EntityQuery/Grammar/IdentityExpression.cs b/src/EntityGraphQL/Compiler/EntityQuery/Grammar/IdentityExpression.cs index fbad1b92..59add8f7 100644 --- a/src/EntityGraphQL/Compiler/EntityQuery/Grammar/IdentityExpression.cs +++ b/src/EntityGraphQL/Compiler/EntityQuery/Grammar/IdentityExpression.cs @@ -43,7 +43,7 @@ internal static Expression MakePropertyCall(Expression context, ISchemaProvider? return Expression.Constant(Enum.Parse(schemaType.TypeDotnet, name)); } var gqlField = schemaType.GetField(name, requestContext); - (var exp, _) = gqlField.GetExpression(gqlField.ResolveExpression!, context, null, null, compileContext, new Dictionary(), null, null, [], false, new Util.ParameterReplacer()); + (var exp, _) = gqlField.GetExpression(gqlField.ResolveExpression!, context, null, null, compileContext, new Dictionary(), null, null, [], false, new Util.ParameterReplacer()); return exp!; } diff --git a/src/EntityGraphQL/Compiler/GqlNodes/BaseGraphQLField.cs b/src/EntityGraphQL/Compiler/GqlNodes/BaseGraphQLField.cs index 6810f775..0290f934 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/BaseGraphQLField.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/BaseGraphQLField.cs @@ -63,7 +63,7 @@ public abstract class BaseGraphQLField : IGraphQLNode, IFieldKey /// /// Arguments from inline in the query - not $ variables /// - public IReadOnlyDictionary Arguments { get; } + public IReadOnlyDictionary Arguments { get; } /// /// True if this field directly has services @@ -77,14 +77,14 @@ public BaseGraphQLField( Expression? nextFieldContext, ParameterExpression? rootParameter, IGraphQLNode? parentNode, - IReadOnlyDictionary? arguments + IReadOnlyDictionary? arguments ) { Name = name; NextFieldContext = nextFieldContext; RootParameter = rootParameter; ParentNode = parentNode; - this.Arguments = arguments ?? new Dictionary(); + this.Arguments = arguments ?? new Dictionary(); this.Schema = schema; Field = field; } diff --git a/src/EntityGraphQL/Compiler/GqlNodes/BaseGraphQLQueryField.cs b/src/EntityGraphQL/Compiler/GqlNodes/BaseGraphQLQueryField.cs index adb678f6..23aa316e 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/BaseGraphQLQueryField.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/BaseGraphQLQueryField.cs @@ -28,7 +28,7 @@ protected BaseGraphQLQueryField( Expression? nextFieldContext, ParameterExpression? rootParameter, IGraphQLNode? parentNode, - IReadOnlyDictionary? arguments + IReadOnlyDictionary? arguments ) : base(schema, field, name, nextFieldContext, rootParameter, parentNode, arguments) { } diff --git a/src/EntityGraphQL/Compiler/GqlNodes/CompileContext.cs b/src/EntityGraphQL/Compiler/GqlNodes/CompileContext.cs index 4f390f6c..46740824 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/CompileContext.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/CompileContext.cs @@ -63,7 +63,7 @@ List listExpressionPath public void AddArgsToCompileContext( IField field, - IReadOnlyDictionary args, + IReadOnlyDictionary args, ParameterExpression? docParam, object? docVariables, ref object? argumentValue, diff --git a/src/EntityGraphQL/Compiler/GqlNodes/ExecutableGraphQLStatement.cs b/src/EntityGraphQL/Compiler/GqlNodes/ExecutableGraphQLStatement.cs index 95e1239b..c5a51af0 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/ExecutableGraphQLStatement.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/ExecutableGraphQLStatement.cs @@ -33,7 +33,7 @@ public abstract class ExecutableGraphQLStatement : IGraphQLNode public IField? Field { get; } public bool HasServices => Field?.Services.Count > 0; - public IReadOnlyDictionary Arguments { get; } + public IReadOnlyDictionary Arguments { get; } public string? Name { get; } @@ -52,7 +52,7 @@ public ExecutableGraphQLStatement(ISchemaProvider schema, string? name, Expressi RootParameter = rootParameter; OpDefinedVariables = opVariables; this.Schema = schema; - Arguments = new Dictionary(); + Arguments = new Dictionary(); if (OpDefinedVariables.Count > 0) { var variableType = LinqRuntimeTypeBuilder.GetDynamicType(OpDefinedVariables.ToDictionary(f => f.Key, f => f.Value.RawType), "docVars"); diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLDirective.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLDirective.cs index e151662e..4b6cca1d 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLDirective.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLDirective.cs @@ -9,10 +9,10 @@ namespace EntityGraphQL.Compiler; public class GraphQLDirective { private readonly IDirectiveProcessor processor; - private readonly Dictionary inlineArgValues; + private readonly Dictionary inlineArgValues; private readonly string name; - public GraphQLDirective(string name, IDirectiveProcessor processor, Dictionary inlineArgValues) + public GraphQLDirective(string name, IDirectiveProcessor processor, Dictionary inlineArgValues) { this.processor = processor; this.inlineArgValues = inlineArgValues; @@ -23,7 +23,7 @@ public GraphQLDirective(string name, IDirectiveProcessor processor, Dictionary args, + IReadOnlyDictionary args, ParameterExpression? docParam, object? docVariables ) @@ -33,7 +33,7 @@ public GraphQLDirective(string name, IDirectiveProcessor processor, Dictionary(); + Arguments = new Dictionary(); } public string Name => "Query Request Root"; @@ -57,7 +57,7 @@ public GraphQLDocument(ISchemaProvider schema) public IField? Field { get; } public bool HasServices => Field?.Services.Count > 0; - public IReadOnlyDictionary Arguments { get; } + public IReadOnlyDictionary Arguments { get; } public ISchemaProvider Schema { get; } diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentStatement.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentStatement.cs index d047c9cb..2a5ffe72 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentStatement.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentStatement.cs @@ -14,7 +14,7 @@ public class GraphQLFragmentStatement : IGraphQLNode public IField? Field { get; } public bool HasServices => Field?.Services.Count > 0; - public IReadOnlyDictionary Arguments { get; } + public IReadOnlyDictionary Arguments { get; } public string Name { get; } @@ -28,7 +28,7 @@ public GraphQLFragmentStatement(ISchemaProvider schema, string name, ParameterEx Name = name; NextFieldContext = selectContext; RootParameter = rootParameter; - Arguments = new Dictionary(); + Arguments = new Dictionary(); Schema = schema; } diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLListSelectionField.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLListSelectionField.cs index 63647f88..cba2cddf 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLListSelectionField.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLListSelectionField.cs @@ -42,7 +42,7 @@ public GraphQLListSelectionField( ParameterExpression? rootParameter, Expression nodeExpression, IGraphQLNode context, - Dictionary? arguments + Dictionary? arguments ) : base(schema, field, name, nextFieldContext, rootParameter, context, arguments) { diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLMutationField.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLMutationField.cs index 979e117d..eec87d26 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLMutationField.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLMutationField.cs @@ -17,7 +17,7 @@ public GraphQLMutationField( ISchemaProvider schema, string name, MutationField mutationField, - Dictionary? args, + Dictionary? args, Expression nextFieldContext, ParameterExpression rootParameter, IGraphQLNode parentNode diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLObjectProjectionField.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLObjectProjectionField.cs index 959ea7b1..d255141b 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLObjectProjectionField.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLObjectProjectionField.cs @@ -39,7 +39,7 @@ public GraphQLObjectProjectionField( Expression nextFieldContext, ParameterExpression rootParameter, IGraphQLNode parentNode, - IReadOnlyDictionary? arguments + IReadOnlyDictionary? arguments ) : base(schema, field, name, nextFieldContext, rootParameter, parentNode, arguments) { } diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLScalarField.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLScalarField.cs index a9635629..c6cc79b4 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLScalarField.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLScalarField.cs @@ -15,7 +15,7 @@ public GraphQLScalarField( Expression nextFieldContext, ParameterExpression? rootParameter, IGraphQLNode parentNode, - IReadOnlyDictionary? arguments + IReadOnlyDictionary? arguments ) : base(schema, field, name, nextFieldContext, rootParameter, parentNode, arguments) { } diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLSubscriptionField.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLSubscriptionField.cs index 99df2c33..af5b8154 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLSubscriptionField.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLSubscriptionField.cs @@ -17,7 +17,7 @@ public GraphQLSubscriptionField( ISchemaProvider schema, string name, SubscriptionField subscriptionField, - Dictionary? args, + Dictionary? args, Expression nextFieldContext, ParameterExpression rootParameter, IGraphQLNode parentNode diff --git a/src/EntityGraphQL/Compiler/GqlNodes/IGraphQLNode.cs b/src/EntityGraphQL/Compiler/GqlNodes/IGraphQLNode.cs index 218ad363..f456fba1 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/IGraphQLNode.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/IGraphQLNode.cs @@ -28,7 +28,7 @@ public interface IGraphQLNode void AddField(BaseGraphQLField field); IField? Field { get; } bool HasServices { get; } - IReadOnlyDictionary Arguments { get; } + IReadOnlyDictionary Arguments { get; } void AddDirectives(IEnumerable graphQLDirectives); /// diff --git a/src/EntityGraphQL/Compiler/Util/ArgumentUtil.cs b/src/EntityGraphQL/Compiler/Util/ArgumentUtil.cs index 8c90a1c3..57de5621 100644 --- a/src/EntityGraphQL/Compiler/Util/ArgumentUtil.cs +++ b/src/EntityGraphQL/Compiler/Util/ArgumentUtil.cs @@ -14,7 +14,7 @@ public static class ArgumentUtil ISchemaProvider schema, string fieldName, IField? field, - IReadOnlyDictionary args, + IReadOnlyDictionary args, IEnumerable argumentDefinitions, Type? argumentsType, ParameterExpression? docParam, @@ -122,7 +122,7 @@ private static void SetArgumentValues(Dictionary values, object internal static object? BuildArgumentFromMember( ISchemaProvider schema, - IReadOnlyDictionary? args, + IReadOnlyDictionary? args, string memberName, Type memberType, object? defaultValue, @@ -139,6 +139,10 @@ IList validationErrors return null; } var item = args[argName]; + if(item is null) + { + return null; + } var constructor = memberType.GetConstructor(new[] { item.GetType() }); if (constructor == null) { diff --git a/src/EntityGraphQL/Extensions/DictionaryExtensions.cs b/src/EntityGraphQL/Extensions/DictionaryExtensions.cs index 59371f8d..079da137 100644 --- a/src/EntityGraphQL/Extensions/DictionaryExtensions.cs +++ b/src/EntityGraphQL/Extensions/DictionaryExtensions.cs @@ -15,4 +15,15 @@ public static Dictionary MergeNew(this IDictiona return result; } + + public static Dictionary MergeNewNullable(this IDictionary source, IReadOnlyDictionary? other) + where TKey : notnull + { + var result = source != null ? source.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) : []; + if (other != null) + foreach (var kvp in other) + result[kvp.Key] = kvp.Value; + + return result; + } } diff --git a/src/EntityGraphQL/Schema/BaseField.cs b/src/EntityGraphQL/Schema/BaseField.cs index 20de8a3b..6add287e 100644 --- a/src/EntityGraphQL/Schema/BaseField.cs +++ b/src/EntityGraphQL/Schema/BaseField.cs @@ -105,7 +105,7 @@ public abstract (Expression? expression, ParameterExpression? argumentParam) Get IGraphQLNode? parentNode, ParameterExpression? schemaContext, CompileContext compileContext, - IReadOnlyDictionary args, + IReadOnlyDictionary args, ParameterExpression? docParam, object? docVariables, IEnumerable directives, diff --git a/src/EntityGraphQL/Schema/Field.cs b/src/EntityGraphQL/Schema/Field.cs index 659680cb..f301e48c 100644 --- a/src/EntityGraphQL/Schema/Field.cs +++ b/src/EntityGraphQL/Schema/Field.cs @@ -167,7 +167,7 @@ public override (Expression? expression, ParameterExpression? argumentParam) Get IGraphQLNode? parentNode, ParameterExpression? schemaContext, CompileContext compileContext, - IReadOnlyDictionary args, + IReadOnlyDictionary args, ParameterExpression? docParam, object? docVariables, IEnumerable directives, @@ -196,7 +196,7 @@ ParameterReplacer replacer } private (Expression? fieldExpression, ParameterExpression? argumentParam) PrepareFieldExpression( - IReadOnlyDictionary args, + IReadOnlyDictionary args, Expression fieldExpression, ParameterReplacer replacer, Expression context, diff --git a/src/EntityGraphQL/Schema/IField.cs b/src/EntityGraphQL/Schema/IField.cs index 862e1787..3bd175c6 100644 --- a/src/EntityGraphQL/Schema/IField.cs +++ b/src/EntityGraphQL/Schema/IField.cs @@ -93,7 +93,7 @@ public interface IField IGraphQLNode? parentNode, ParameterExpression? schemaContext, CompileContext compileContext, - IReadOnlyDictionary args, + IReadOnlyDictionary args, ParameterExpression? docParam, object? docVariables, IEnumerable directives, diff --git a/src/EntityGraphQL/Schema/MethodField.cs b/src/EntityGraphQL/Schema/MethodField.cs index f71b768b..f0fed1a4 100644 --- a/src/EntityGraphQL/Schema/MethodField.cs +++ b/src/EntityGraphQL/Schema/MethodField.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; @@ -65,7 +66,7 @@ SchemaBuilderOptions options public virtual async Task CallAsync( object? context, - IReadOnlyDictionary? gqlRequestArgs, + IReadOnlyDictionary? gqlRequestArgs, IServiceProvider? serviceProvider, ParameterExpression? variableParameter, object? docVariables, @@ -80,6 +81,7 @@ ExecutionOptions executionOptions var argsToValidate = new Dictionary(); object? argInstance = null; var validationErrors = new List(); + var setProperties = new List(); // add parameters and any DI services foreach (var p in Method.GetParameters()) @@ -91,7 +93,7 @@ ExecutionOptions executionOptions Schema, Name, this, - gqlRequestArgs ?? new Dictionary(), + gqlRequestArgs ?? new Dictionary(), Arguments.Values, p.ParameterType, variableParameter, @@ -99,9 +101,24 @@ ExecutionOptions executionOptions validationErrors )!; allArgs.Add(argInstance); + + if (p.ParameterType.BaseType == typeof(PropertySetTrackingDto)) + { + ((PropertySetTrackingDto)argInstance).MarkAsSet(gqlRequestArgs?.Keys ?? []); + } } else if (gqlRequestArgs != null && Arguments.TryGetValue(p.Name!, out var argField)) { + //Dictionary nonNullGqlRequestArgs = new(); + //foreach (var arg in gqlRequestArgs) + //{ + // if (arg.Value is null) + // { + // continue; + // } + // nonNullGqlRequestArgs.Add(arg.Key, arg.Value); + //} + var value = ArgumentUtil.BuildArgumentFromMember(Schema, gqlRequestArgs, argField.Name, argField.RawType, argField.DefaultValue, validationErrors); if (docVariables != null) { @@ -193,7 +210,7 @@ public override (Expression? expression, ParameterExpression? argumentParam) Get IGraphQLNode? parentNode, ParameterExpression? schemaContext, CompileContext? compileContext, - IReadOnlyDictionary args, + IReadOnlyDictionary args, ParameterExpression? docParam, object? docVariables, IEnumerable directives, diff --git a/src/EntityGraphQL/Schema/PropertyTracker/IPropertySetTrackingDto.cs b/src/EntityGraphQL/Schema/PropertyTracker/IPropertySetTrackingDto.cs new file mode 100644 index 00000000..342c58b0 --- /dev/null +++ b/src/EntityGraphQL/Schema/PropertyTracker/IPropertySetTrackingDto.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace EntityGraphQL.Schema; +public interface IPropertySetTrackingDto +{ + bool IsSet(string propertyName); + void MarkAsSet(IEnumerable propertiesName); + void MarkAsSet(string propertyName); +} \ No newline at end of file diff --git a/src/EntityGraphQL/Schema/PropertyTracker/PropertySetTrackingDto.cs b/src/EntityGraphQL/Schema/PropertyTracker/PropertySetTrackingDto.cs new file mode 100644 index 00000000..e69a2d2b --- /dev/null +++ b/src/EntityGraphQL/Schema/PropertyTracker/PropertySetTrackingDto.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace EntityGraphQL.Schema; + +public class PropertySetTrackingDto : IPropertySetTrackingDto +{ + private HashSet setProperties = new(StringComparer.OrdinalIgnoreCase); + + public void MarkAsSet(string propertyName) => setProperties.Add(propertyName); + public void MarkAsSet(IEnumerable propertiesName) + { + foreach (var prop in propertiesName) + { + setProperties.Add(prop); + } + } + + public bool IsSet(string propertyName) => setProperties.Contains(propertyName); +} diff --git a/src/examples/demo/Mutations/DemoMutations.cs b/src/examples/demo/Mutations/DemoMutations.cs index f0f06089..8a39b648 100644 --- a/src/examples/demo/Mutations/DemoMutations.cs +++ b/src/examples/demo/Mutations/DemoMutations.cs @@ -62,6 +62,56 @@ public Expression> AddMovie(DemoContext db, AddMovieArg return ctx => ctx.Movies.First(m => m.Id == movie.Id); } + + [GraphQLMutation("Add a new Movie object")] + public Expression>? UpdateMovie(DemoContext db, UpdateMovieArgs args, IGraphQLValidator validator) + { + var movie = db.Movies.FirstOrDefault(x => x.Id == args.Id); + if(movie is null) + { + validator.AddError("Movie not found"); + return null; + } + + if (args.IsSet(nameof(args.Name))) + { + if (!string.IsNullOrEmpty(args.Name)) + { + movie.Name = args.Name; + } + } + if (args.IsSet(nameof(args.Genre))) + { + if (args.Genre.HasValue) + { + movie.Genre = args.Genre.Value; + } + } + if (args.IsSet(nameof(args.Released))) + { + if (args.Released.HasValue) + { + movie.Released = args.Released.Value; + } + } + if (args.IsSet(nameof(args.Rating))) + { + if (args.Rating.HasValue) + { + movie.Rating = args.Rating.Value; + } + } + // Cannot be null if not set + if (args.IsSet(nameof(args.DirectorId))) + { + movie.DirectorId = args.DirectorId; + } + + db.Movies.Update(movie); + db.SaveChanges(); + return ctx => ctx.Movies.First(m => m.Id == movie.Id); + } + [GraphQLMutation] public Expression>? AddActor(DemoContext db, [GraphQLArguments] AddActorArgs args, IGraphQLValidator validator) { @@ -157,7 +207,6 @@ public class AddMovieArgs public required string Name { get; set; } public double Rating { get; set; } public DateTime Released; - public Detail? Details { get; set; } } public class Detail @@ -165,6 +214,23 @@ public class Detail public required string Description { get; set; } } +[GraphQLArguments] +public class UpdateMovieArgs : PropertySetTrackingDto +{ + public required long Id { get; set; } + public Genre? Genre; + public string? Name { get; set; } + public double? Rating { get; set; } + public DateTime? Released; + public uint? DirectorId { get; set; } +} + +public class MovideDirector +{ + public long Id { get; set; } +} + + public class AddActorArgs { public required string FirstName { get; set; } diff --git a/src/examples/demo/schema.graphql b/src/examples/demo/schema.graphql index 87fa314e..a1806484 100644 --- a/src/examples/demo/schema.graphql +++ b/src/examples/demo/schema.graphql @@ -230,10 +230,12 @@ type Mutation { addActor2(firstName: String!, lastName: String!, movieId: Int!): [Person!]! addActor3(names: [String!]!, movieId: Int!): [Person!]! """Add a new Movie object""" - addMovie(name: String!, rating: Float!, details: Detail, genre: Genre!, released: Date!): Movie! + addMovie(name: String!, rating: Float!, genre: Genre!, released: Date!): Movie! """Example of a mutation that takes 0 arguments""" exampleNoArgs: Movie! """Example of a mutation that does not use the context or arguments but does use registered services""" exampleNoArgsWithService: Int! + """Add a new Movie object""" + updateMovie(id: Int!, name: String, rating: Float, directorId: Int, genre: Genre, released: Date): Movie! } From 444ba8b1497c51b20a13a44e74ce016574972a8b Mon Sep 17 00:00:00 2001 From: Alexander Bruslenko Date: Wed, 21 May 2025 07:27:09 +0300 Subject: [PATCH 2/3] MethodField code cleaned, demo mutation update --- src/EntityGraphQL/Schema/MethodField.cs | 12 +---- src/examples/demo/Mutations/DemoMutations.cs | 50 ++++++-------------- 2 files changed, 15 insertions(+), 47 deletions(-) diff --git a/src/EntityGraphQL/Schema/MethodField.cs b/src/EntityGraphQL/Schema/MethodField.cs index f0fed1a4..e0540d0a 100644 --- a/src/EntityGraphQL/Schema/MethodField.cs +++ b/src/EntityGraphQL/Schema/MethodField.cs @@ -108,17 +108,7 @@ ExecutionOptions executionOptions } } else if (gqlRequestArgs != null && Arguments.TryGetValue(p.Name!, out var argField)) - { - //Dictionary nonNullGqlRequestArgs = new(); - //foreach (var arg in gqlRequestArgs) - //{ - // if (arg.Value is null) - // { - // continue; - // } - // nonNullGqlRequestArgs.Add(arg.Key, arg.Value); - //} - + { var value = ArgumentUtil.BuildArgumentFromMember(Schema, gqlRequestArgs, argField.Name, argField.RawType, argField.DefaultValue, validationErrors); if (docVariables != null) { diff --git a/src/examples/demo/Mutations/DemoMutations.cs b/src/examples/demo/Mutations/DemoMutations.cs index 8a39b648..4fd980f9 100644 --- a/src/examples/demo/Mutations/DemoMutations.cs +++ b/src/examples/demo/Mutations/DemoMutations.cs @@ -73,39 +73,23 @@ public Expression> AddMovie(DemoContext db, AddMovieArg return null; } - if (args.IsSet(nameof(args.Name))) - { - if (!string.IsNullOrEmpty(args.Name)) - { - movie.Name = args.Name; - } - } - if (args.IsSet(nameof(args.Genre))) - { - if (args.Genre.HasValue) - { - movie.Genre = args.Genre.Value; - } - } - if (args.IsSet(nameof(args.Released))) - { - if (args.Released.HasValue) - { - movie.Released = args.Released.Value; - } - } - if (args.IsSet(nameof(args.Rating))) - { - if (args.Rating.HasValue) - { - movie.Rating = args.Rating.Value; - } - } // Cannot be null if not set if (args.IsSet(nameof(args.DirectorId))) - { movie.DirectorId = args.DirectorId; - } + + if (!string.IsNullOrEmpty(args.Name)) + movie.Name = args.Name; + + if (args.Genre.HasValue) + movie.Genre = args.Genre.Value; + + + if (args.Released.HasValue) + movie.Released = args.Released.Value; + + + if (args.Rating.HasValue) + movie.Rating = args.Rating.Value; db.Movies.Update(movie); db.SaveChanges(); @@ -225,12 +209,6 @@ public class UpdateMovieArgs : PropertySetTrackingDto public uint? DirectorId { get; set; } } -public class MovideDirector -{ - public long Id { get; set; } -} - - public class AddActorArgs { public required string FirstName { get; set; } From e2fe50c41ca436f8dce237266df7e3ec99730bbf Mon Sep 17 00:00:00 2001 From: BruslenkoAM Date: Thu, 22 May 2025 12:54:29 +0300 Subject: [PATCH 3/3] IPropertySetTrackingDto check --- src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs | 4 +--- src/EntityGraphQL/Schema/MethodField.cs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs b/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs index 6a9464a9..85192260 100644 --- a/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs +++ b/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs @@ -372,9 +372,7 @@ private GraphQLObjectProjectionField BuildDynamicSelectForObjectGraph( { throw new EntityGraphQLCompilerException($"No argument '{argName}' found on field '{field.Name}'"); } - var r = ParseArgument(argName, field, arg); - //if (r != null) - args.Add(argName, r); + args.Add(argName, ParseArgument(argName, field, arg)); } return args; } diff --git a/src/EntityGraphQL/Schema/MethodField.cs b/src/EntityGraphQL/Schema/MethodField.cs index e0540d0a..eab1ae7c 100644 --- a/src/EntityGraphQL/Schema/MethodField.cs +++ b/src/EntityGraphQL/Schema/MethodField.cs @@ -102,9 +102,9 @@ ExecutionOptions executionOptions )!; allArgs.Add(argInstance); - if (p.ParameterType.BaseType == typeof(PropertySetTrackingDto)) + if (typeof(IPropertySetTrackingDto).IsAssignableFrom(p.ParameterType)) { - ((PropertySetTrackingDto)argInstance).MarkAsSet(gqlRequestArgs?.Keys ?? []); + ((IPropertySetTrackingDto)argInstance).MarkAsSet((gqlRequestArgs ?? new Dictionary()).Keys); } } else if (gqlRequestArgs != null && Arguments.TryGetValue(p.Name!, out var argField))