Skip to content

Using records as a projection fails on QueryableProjectionScopeExtensions #6487

@CasperCBroeren

Description

@CasperCBroeren

Is there an existing issue for this?

  • I have searched the existing issues

Product

Hot Chocolate

Describe the bug

If you use a record in a projection, it will fail because of trying to use the default constructor on it.
Type 'PriceData' does not have a default constructor (Parameter 'type')

Steps to reproduce

  1. Create a record
  2. Create a query
  3. Mark query as projection
  4. Call the query
  5. Response has a result with error node

Relevant log output

message
: 
"Type 'PriceData' does not have a default constructor (Parameter 'type')"
stackTrace
: 
"   at System.Linq.Expressions.Expression.New(Type type)\r\n   at HotChocolate.Data.Projections.Expressions.QueryableProjectionScopeExtensions.CreateMemberInit(QueryableProjectionScope scope)\r\n   at HotChocolate.Data.Projections.Expressions.QueryableProjectionScopeExtensions.CreateMemberInitLambda(QueryableProjection 

Additional Context?

You can create records with Expressions and turn them in a MemberInitExpression. Still I don't know if this safe and sound.
Below is an attempt to patch CreateMemberInit but this is untested

private static Expression GetMemberInit(Object val)
    {
        var isRecord = ((TypeInfo) val.Key).DeclaredProperties.Any(x => x.Name == "EqualityContract");
        if (isRecord)
        {
            var ctor = val.Key.GetConstructors()[0];
            return Expression.MemberInit(Expression.New(ctor, val.Value.Select(x => x.Expression)));
        }
        else
        {
            var ctor = Expression.New(val.Key);
            return Expression.MemberInit(ctor, val.Value);
        }
    }

public static Expression CreateMemberInit(this QueryableProjectionScope scope)
    {
        if (scope.HasAbstractTypes())
        {
            Expression lastValue = Expression.Default(scope.RuntimeType);

            foreach (var val in scope.GetAbstractTypes())
            {
                Expression memberInit = GetMemberInit(val);

                lastValue = Expression.Condition(
                    Expression.TypeIs(scope.Instance.Peek(), val.Key),
                    Expression.Convert(memberInit, scope.RuntimeType),
                    lastValue);
            }

            return lastValue;
        }
        else
        {
            var ctor = Expression.New(scope.RuntimeType);
            return Expression.MemberInit(ctor, scope.Level.Peek());
        }
    }

Version

13.5.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: DataIssue is related to filtering, sorting, pagination or projections🌶️ hot chocolate

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions