Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sort with Variables tries to instantiate an object from the target collection, fails if no parameterless constructor available #6545

Open
1 task done
alexmazzariol-scai opened this issue Sep 20, 2023 · 3 comments · May be fixed by #6660
Labels
Area: Data Issue is related to filtering, sorting, pagination or projections 🐛 bug Something isn't working 🌶️ hot chocolate

Comments

@alexmazzariol-scai
Copy link

alexmazzariol-scai commented Sep 20, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Product

Hot Chocolate

Describe the bug

I have a business backend that deals, among other entities, with subjects and their addresses. In the database, handled via Entity Framework Core 7, there is a relation between the subject and its addresses, so that inside the Subject entity there is an "Addresses" navigation property which is defined as an ICollection

. Both classes have no parameterless constructor to fill non-nullable property values.

When returning an IQueryable to HotChocolate, I have the query set up like this:

  [UsePaging(IncludeTotalCount = true, MaxPageSize = 100)]
  [UseFiltering(typeof(SubjectFilterType))]
  [UseSorting(typeof(SubjectSortInputType))]
  public IQueryable<ISubject> ListSubjects([Service] IReadRepository<Subject> repo)
    => repo.AsQueryable(new SubjectsNonDeletedSpec());

The readRepository wraps and ultimately returns the DbSet from Entity Framework, just mixing in a .Where condition on the DeletionDate field.

For the purpose of the bug, I don't think filtering middleware is an issue; the sorting middleware is configured with these settings:

  public class SubjectSortInputType : SortInputType<Subject>
  {
    protected override void Configure(ISortInputTypeDescriptor<Subject> descriptor)
    {
      descriptor.BindFieldsImplicitly();

      descriptor
        .Field(f => f.Addresses.FirstOrDefault(address => address.AddressType == AddressType.LegalResidential))
        .Type<AddressSortInputType>()
        .Name("legalResidentialAddress");
    }
  }

In this way, a virtual "legalResidentialAddress" field is added to the GraphQL schema, and it is supposed to be able to be used for sorting.

Indeed when the query is like this:

  query subsorting {
    subject {
      listSubjects(order: { legalResidentialAddress: { cityName: ASC }})
      {
        nodes {
          shorthandDescription
        }
      }
    }
  }

the expected results are received.

But when the query includes variables, like this:

  query subsorting($order: [SubjectSortInput!]) {
    subject {
      listSubjects(order: $order)
      {
        nodes {
          shorthandDescription
        }
      }
    }
  }

  {
    "order": [
      {
        "legalResidentialAddress": {
          "cityName": "ASC"
        }
      }
    ]
  }

An exception is raised and no errors are returned. Exception details and stacktrace is below.

I don't expect the difference between using and not using variables to produce the instantiation of an object from the target class of the sorting collection; why is it happening? Is a parameterless constructor actually needed?

Steps to reproduce

Due to the complexity of producing a sample showcase github repo, it will be added if the above description is not enough to understand the issue clearly.

Relevant log output

[19:32:26 INF] Now listening on: http://localhost:5000
   [19:32:26 INF] Application started. Press Ctrl+C to shut down.
   [19:32:26 INF] Hosting environment: Development
   [19:32:26 INF] Content root path: /workspace/backend/src/Web/bin/Debug/net7.0
   [19:32:32 ERR] Error on GraphQL request
   HotChocolate.GraphQLException: Variable `order` got an invalid value.
      at HotChocolate.Execution.Processing.VariableCoercionHelper.CoerceVariableValue(VariableDefinitionNode variableDefinition, IInputType variableType, Object value)
      at HotChocolate.Execution.Processing.VariableCoercionHelper.CoerceVariableValues(ISchema schema, IReadOnlyList`1 variableDefinitions, IReadOnlyDictionary`2 values, IDictionary`2 coercedValues)
      at HotChocolate.Execution.Pipeline.PipelineTools.CoerceVariables(IRequestContext context, VariableCoercionHelper coercionHelper, IReadOnlyList`1 variableDefinitions)
      at HotChocolate.Execution.Pipeline.OperationVariableCoercionMiddleware.InvokeAsync(IRequestContext context)
      at HotChocolate.Execution.Pipeline.OperationResolverMiddleware.InvokeAsync(IRequestContext context)
      at HotChocolate.Execution.Pipeline.OperationComplexityMiddleware.InvokeAsync(IRequestContext context)
      at HotChocolate.Execution.Pipeline.OperationCacheMiddleware.InvokeAsync(IRequestContext context)
      at HotChocolate.Execution.Pipeline.DocumentValidationMiddleware.InvokeAsync(IRequestContext context)
      at HotChocolate.Execution.Pipeline.DocumentParserMiddleware.InvokeAsync(IRequestContext context)
      at HotChocolate.Execution.Pipeline.DocumentCacheMiddleware.InvokeAsync(IRequestContext context)
      at HotChocolate.Execution.Pipeline.TimeoutMiddleware.InvokeAsync(IRequestContext context)
      at HotChocolate.Execution.Pipeline.ExceptionMiddleware.InvokeAsync(IRequestContext context)
   [19:32:32 ERR] Single error details: HC0016 Variable `order` got an invalid value. at path null
   System.MissingMethodException: Cannot dynamically create an instance of type 'XYZ.Core.Anag.SubjectAggregate.Address'. Reason: No parameterless constructor defined.
      at System.RuntimeType.ActivatorCache..ctor(RuntimeType rt)
      at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
      at HotChocolate.Utilities.DictionaryToObjectConverter.VisitObject(IReadOnlyDictionary`2 dictionary, ConverterContext context)
      at HotChocolate.Utilities.DictionaryVisitor`1.Visit(Object value, TContext context)
      at HotChocolate.Utilities.DictionaryToObjectConverter.Convert(Object from, Type to)
      at HotChocolate.Types.InputParser.ConvertValue(Type requestedType, Object value)
      at HotChocolate.Types.InputParser.ParseObject(IValueNode resultValue, InputObjectType type, Path path, Int32 stack, Boolean defaults)
      at HotChocolate.Types.InputParser.ParseLiteralInternal(IValueNode value, IType type, Path path, Int32 stack, Boolean defaults, IInputFieldInfo field)
      at HotChocolate.Types.InputParser.ParseLiteralInternal(IValueNode value, IType type, Path path, Int32 stack, Boolean defaults, IInputFieldInfo field)
      at HotChocolate.Types.InputParser.ParseList(IValueNode resultValue, ListType type, Path path, Int32 stack, Boolean defaults, IInputFieldInfo field)
      at HotChocolate.Types.InputParser.ParseLiteralInternal(IValueNode value, IType type, Path path, Int32 stack, Boolean defaults, IInputFieldInfo field)
      at HotChocolate.Types.InputParser.ParseLiteral(IValueNode value, IType type, Path path)
      at HotChocolate.Execution.Processing.VariableCoercionHelper.CoerceVariableValue(VariableDefinitionNode variableDefinition, IInputType variableType, Object value)

Additional Context?

No response

Version

13.2.1

@alexmazzariol-scai alexmazzariol-scai added the 🐛 bug Something isn't working label Sep 20, 2023
@michaelstaib michaelstaib added 🌶️ hot chocolate Area: Data Issue is related to filtering, sorting, pagination or projections labels Sep 25, 2023
@alex-mazzariol
Copy link

The issue still persists with version 13.5.1; I made a showcase application in https://github.com/alex-mazzariol/hotchocolate-issue-6545.

If anybody can point me in the right direction, since I also noticed some recent work around the classes that digest the variable values, I might be even able to find the problem and propose a fix...

@alex-mazzariol
Copy link

alex-mazzariol commented Oct 27, 2023

As far as I can gather debugging, it seems like the VariableCoercionHelper asks the InputParser to convert the variables into the appropriate input type, but for some reason, when it comes to mapping the added field (inside InputParser.ParseObject) the type from the Field<T>() lambda is taken into consideration, and not the type specified via the .Type<AddressSortInputType>().

@alex-mazzariol
Copy link

I've been able to find and, apparently, solve the issue - inside src/HotChocolate/Core/src/Types/Types/InputParser.cs around line 260:

var value = ParseLiteralInternal(
    literal,
    field.Type,
    fieldPath,
    stack,
    defaults,
    field);
value = FormatValue(field, value);
value = ConvertValue(field.RuntimeType, value);

if (field.IsOptional)
{
    value = new Optional(value, true);
}

The issue is in the call to ConvertValue(field.RuntimeType, value); where the type parameter is just the RuntimeType of the field, which will be the Address type that cannot be instantiated.

If I replace the parameter with field.Type.RuntimeType, so that the piece of code becomes

var value = ParseLiteralInternal(
    literal,
    field.Type,
    fieldPath,
    stack,
    defaults,
    field);
value = FormatValue(field, value);
value = ConvertValue(field.Type.RuntimeType, value);

if (field.IsOptional)
{
    value = new Optional(value, true);
}

And build HotChocolate again, I am able to sort (correctly) with the input type applied to the correct field, and can continue sorting by the other existing fields as well.

Could someone verify the one-line fix with the tests? I am unable to find instructions on how to run them, and opening a PR for a oneliner might be a little too complex for the result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Data Issue is related to filtering, sorting, pagination or projections 🐛 bug Something isn't working 🌶️ hot chocolate
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants