Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,47 +81,63 @@ public static Expression CreateSelection(
Expression source,
Type sourceType)
{
var elementType = GetElementType(sourceType) ?? scope.RuntimeType;
var selector = CreateMemberInitLambda(scope, elementType);

var selection = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.Select),
[
scope.RuntimeType,
scope.RuntimeType
elementType
],
source,
scope.CreateMemberInitLambda());
selector);

if (sourceType.IsArray)
{
return ToArray(scope, selection);
return ToArray(selection, elementType);
}

if (TryGetSetType(sourceType, out var setType))
{
return ToSet(selection, setType);
}

return ToList(scope, selection);
return ToList(selection, elementType);
}

private static Expression ToArray(QueryableProjectionScope scope, Expression source)
private static Expression CreateMemberInitLambda(
QueryableProjectionScope scope,
Type targetType)
{
var projection = scope.CreateMemberInit();
if (targetType != scope.RuntimeType)
{
projection = Expression.Convert(projection, targetType);
}

return Expression.Lambda(projection, scope.Parameter);
}

private static Expression ToArray(Expression source, Type elementType)
{
return Expression.Call(
typeof(Enumerable),
nameof(Enumerable.ToArray),
[
scope.RuntimeType
elementType
],
source);
}

private static Expression ToList(QueryableProjectionScope scope, Expression source)
private static Expression ToList(Expression source, Type elementType)
{
return Expression.Call(
typeof(Enumerable),
nameof(Enumerable.ToList),
[
scope.RuntimeType
elementType
],
source);
}
Expand Down Expand Up @@ -171,4 +187,19 @@ private static bool TryGetSetType(
setType = null;
return false;
}

private static Type? GetElementType(Type type)
{
if (type.IsArray)
{
return type.GetElementType();
}

if (type.IsGenericType)
{
return type.GetGenericArguments()[0];
}

return null;
}
}
117 changes: 117 additions & 0 deletions src/HotChocolate/Data/test/Data.Tests/Issue5528ReproTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using HotChocolate.Execution;
using HotChocolate.Types;
using Microsoft.Extensions.DependencyInjection;

namespace HotChocolate.Data;

public class Issue5528ReproTests
{
[Fact]
public async Task List_Of_Union_With_Fluent_Api_And_Projection_Does_Not_Throw()
{
var executor = await new ServiceCollection()
.AddGraphQL()
.AddProjections()
.AddQueryType<Query>()
.AddType<TenantType>()
.AddType<FileEntryType>()
.AddType<FolderEntryType>()
.AddType<FileOrFolderUnionType>()
.ModifyRequestOptions(o => o.IncludeExceptionDetails = true)
.BuildRequestExecutorAsync();

var result = await executor.ExecuteAsync(
"""
{
tenants {
id
entries {
... on FileEntry {
name
fileSize
}
... on FolderEntry {
name
childCount
}
}
}
}
""");

var operationResult = result.ExpectOperationResult();
Assert.Empty(operationResult.Errors ?? []);
}

public class Query
{
[UseProjection]
public IQueryable<Tenant> GetTenants()
=> Data.AsQueryable();
}

public class Tenant
{
public int Id { get; set; }

public List<Entry> Entries { get; set; } = [];
}

public abstract class Entry
{
public string Name { get; set; } = string.Empty;
}

public class FileEntry : Entry
{
public int FileSize { get; set; }
}

public class FolderEntry : Entry
{
public int ChildCount { get; set; }
}

public class TenantType : ObjectType<Tenant>
{
protected override void Configure(IObjectTypeDescriptor<Tenant> descriptor)
{
descriptor.Field(t => t.Entries).Type<ListType<FileOrFolderUnionType>>();
}
}

public class FileEntryType : ObjectType<FileEntry>;

public class FolderEntryType : ObjectType<FolderEntry>;

public class FileOrFolderUnionType : UnionType
{
protected override void Configure(IUnionTypeDescriptor descriptor)
{
descriptor.Name("FileOrFolder");
descriptor.Type<FileEntryType>();
descriptor.Type<FolderEntryType>();
}
}

private static readonly Tenant[] Data =
[
new()
{
Id = 1,
Entries =
[
new FileEntry
{
Name = "README.md",
FileSize = 123
},
new FolderEntry
{
Name = "src",
ChildCount = 3
}
]
}
];
}
Loading