ChilliCream / ChilliCream/graphql-platform
Using records as a projection fails on QueryableProjectionScopeExtensions
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 5.8k
- Forks
- 810
- Avg merge
- 15h 39m
- Merged PRs (30d)
- 98
Description
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
- Create a record
- Create a query
- Mark query as projection
- Call the query
- 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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in QueryableProjectionScopeExtensions.CreateMemberInit, using the stack trace and reproduction steps. Reproduce the failure with a C# record used in a projection, then inspect how expressions are constructed for types without default constructors. Done means the projection query returns its record result without the default-constructor error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, graphql
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100