Skip to content

Commit

Permalink
Introduced HotChocolate.Data.AutoMapper (#4563)
Browse files Browse the repository at this point in the history
  • Loading branch information
PascalSenn authored Dec 21, 2021
1 parent 1413abe commit 3217ebd
Show file tree
Hide file tree
Showing 23 changed files with 1,061 additions and 10 deletions.
30 changes: 30 additions & 0 deletions src/HotChocolate/Data/HotChocolate.Data.sln
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.AspNetCore", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.AspNetCore.Tests", "..\AspNetCore\test\AspNetCore.Tests\HotChocolate.AspNetCore.Tests.csproj", "{52C8DCA7-E000-41AD-B05C-3B3C08F7DC46}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Data.AutoMapper", "src\AutoMapper\HotChocolate.Data.AutoMapper.csproj", "{0AB70663-9D52-4415-B265-0D1F001D7576}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Data.AutoMapper.Tests", "test\Data.AutoMapper.Tests\HotChocolate.Data.AutoMapper.Tests.csproj", "{F793AC13-0500-492A-914D-4229F6AE0687}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -132,6 +136,8 @@ Global
{5B36B9E9-BC55-4A4D-B58F-9311581C008B} = {4EE990B2-C327-46DA-8FE8-F95AC228E47F}
{4D36CB01-FA11-4961-BAE4-0D1449ED7CCB} = {882EC02D-5E1D-41F5-AD9F-AA06E31D133A}
{52C8DCA7-E000-41AD-B05C-3B3C08F7DC46} = {882EC02D-5E1D-41F5-AD9F-AA06E31D133A}
{0AB70663-9D52-4415-B265-0D1F001D7576} = {91887A91-7B1C-4287-A1E0-BD4E0DAF24C7}
{F793AC13-0500-492A-914D-4229F6AE0687} = {4EE990B2-C327-46DA-8FE8-F95AC228E47F}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D68A0AB9-871A-487B-8D12-1A7544D81B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -520,5 +526,29 @@ Global
{52C8DCA7-E000-41AD-B05C-3B3C08F7DC46}.Release|x64.Build.0 = Release|Any CPU
{52C8DCA7-E000-41AD-B05C-3B3C08F7DC46}.Release|x86.ActiveCfg = Release|Any CPU
{52C8DCA7-E000-41AD-B05C-3B3C08F7DC46}.Release|x86.Build.0 = Release|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Debug|x64.ActiveCfg = Debug|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Debug|x64.Build.0 = Debug|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Debug|x86.ActiveCfg = Debug|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Debug|x86.Build.0 = Debug|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Release|Any CPU.Build.0 = Release|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Release|x64.ActiveCfg = Release|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Release|x64.Build.0 = Release|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Release|x86.ActiveCfg = Release|Any CPU
{0AB70663-9D52-4415-B265-0D1F001D7576}.Release|x86.Build.0 = Release|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Debug|x64.ActiveCfg = Debug|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Debug|x64.Build.0 = Debug|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Debug|x86.ActiveCfg = Debug|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Debug|x86.Build.0 = Debug|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Release|Any CPU.Build.0 = Release|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Release|x64.ActiveCfg = Release|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Release|x64.Build.0 = Release|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Release|x86.ActiveCfg = Release|Any CPU
{F793AC13-0500-492A-914D-4229F6AE0687}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using HotChocolate.Data.Projections.Expressions;
using HotChocolate.Resolvers;
using static HotChocolate.Data.Projections.Expressions.QueryableProjectionProvider;

namespace HotChocolate.Data;

/// <summary>
/// Common extensions for automapper and <see cref="IQueryable{T}"/>
/// </summary>
public static class AutoMapperQueryableExtensions
{
/// <summary>
/// Extension method to project from a queryable using the <see cref="IResolverContext"/>
/// to project <typeparamref name="TSource"/> into <typeparamref name="TResult"/> based on
/// the GraphQL selection.
/// </summary>
/// <param name="queryable">The Queryable that holds the selection</param>
/// <param name="context">The resolver context of the resolver</param>
/// <typeparam name="TSource">The source type</typeparam>
/// <typeparam name="TResult">The result type</typeparam>
/// <returns>The projected queryable</returns>
public static IQueryable<TResult> ProjectTo<TSource, TResult>(
this IQueryable<TSource> queryable,
IResolverContext context)
{
IMapper mapper = context.Service<IMapper>();

// ensure projections are only applied once
context.LocalContextData = context.LocalContextData.SetItem(SkipProjectionKey, true);

QueryableProjectionContext visitorContext =
new(context, context.ObjectType, context.Selection.Field.Type.UnwrapRuntimeType());

QueryableProjectionVisitor.Default.Visit(visitorContext);

Expression<Func<TResult, object>> projection = visitorContext.Project<TResult, object>();

return queryable.ProjectTo(mapper.ConfigurationProvider, projection);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>HotChocolate.Data.AutoMapper</PackageId>
<AssemblyName>HotChocolate.Data.AutoMapper</AssemblyName>
<RootNamespace>HotChocolate.Data</RootNamespace>
<NoWarn>$(NoWarn);CA1062</NoWarn>
<TargetFrameworks Condition="'$(IsMacOsArm)' != 'true'">net6.0; net5.0; netstandard2.1</TargetFrameworks>
<TargetFrameworks Condition="'$(IsMacOsArm)' == 'true'">net6.0</TargetFrameworks>
<Description>Contains extensions for easier integration of AutoMapper into HotChocolate</Description>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\Core\src\Core\HotChocolate.Core.csproj" />
<ProjectReference Include="..\Data\HotChocolate.Data.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="10.0.0" />
</ItemGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\MSBuild\HotChocolate.Data.props" Pack="true" PackagePath="build/HotChocolate.Data.EntityFramework.props" Visible="false" />
<None Include="$(MSBuildThisFileDirectory)..\MSBuild\HotChocolate.Data.targets" Pack="true" PackagePath="build/HotChocolate.Data.EntityFramework.targets" Visible="false" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,16 @@ public static Expression<Func<T, T>> Project<T>(this QueryableProjectionContext

throw ProjectionConvention_CouldNotProject();
}

public static Expression<Func<T, TTarget>> Project<T, TTarget>(
this QueryableProjectionContext context)
where T : TTarget
{
if (context.TryGetQueryableScope(out QueryableProjectionScope? scope))
{
return scope.Project<T, TTarget>();
}

throw ProjectionConvention_CouldNotProject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,35 @@ namespace HotChocolate.Data.Projections.Expressions;

public static class QueryableProjectionScopeExtensions
{
/// <summary>
/// Creates an expression based on the result stored on <see cref="QueryableProjectionScope"/>.
/// </summary>
/// <param name="scope">The scope that contains the projection information</param>
/// <typeparam name="T">The target type</typeparam>
/// <returns>An expression</returns>
public static Expression<Func<T, T>> Project<T>(this QueryableProjectionScope scope)
{
return (Expression<Func<T, T>>)scope.CreateMemberInitLambda();
}
=> (Expression<Func<T, T>>)scope.CreateMemberInitLambda();

/// <summary>
/// Creates an expression based on the result stored on <see cref="QueryableProjectionScope"/>.
/// Casts the result onto <typeparamref name="TTarget"/> in the lambda
/// </summary>
/// <param name="scope">The scope that contains the projection information</param>
/// <typeparam name="T">The target type</typeparam>
/// <typeparam name="TTarget">The target result type of the expression</typeparam>
/// <returns></returns>
public static Expression<Func<T, TTarget>> Project<T, TTarget>(
this QueryableProjectionScope scope)
where T : TTarget
=> (Expression<Func<T, TTarget>>)scope.CreateMemberInitLambda<TTarget>();

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

foreach (KeyValuePair<Type, Queue<MemberAssignment>> val in
scope.GetAbstractTypes())
foreach (KeyValuePair<Type, Queue<MemberAssignment>> val in scope.GetAbstractTypes())
{
NewExpression ctor = Expression.New(val.Key);
Expression memberInit = Expression.MemberInit(ctor, val.Value);
Expand All @@ -46,6 +62,12 @@ public static Expression CreateMemberInitLambda(this QueryableProjectionScope sc
return Expression.Lambda(scope.CreateMemberInit(), scope.Parameter);
}

private static Expression CreateMemberInitLambda<T>(this QueryableProjectionScope scope)
{
Expression converted = Expression.Convert(scope.CreateMemberInit(), typeof(T));
return Expression.Lambda(converted, scope.Parameter);
}

public static Expression CreateSelection(
this QueryableProjectionScope scope,
Expression source,
Expand All @@ -56,8 +78,8 @@ public static Expression CreateSelection(
nameof(Enumerable.Select),
new[]
{
scope.RuntimeType,
scope.RuntimeType
scope.RuntimeType,
scope.RuntimeType
},
source,
scope.CreateMemberInitLambda());
Expand All @@ -82,7 +104,7 @@ private static Expression ToArray(QueryableProjectionScope scope, Expression sou
nameof(Enumerable.ToArray),
new[]
{
scope.RuntimeType
scope.RuntimeType
},
source);
}
Expand All @@ -94,7 +116,7 @@ private static Expression ToList(QueryableProjectionScope scope, Expression sour
nameof(Enumerable.ToList),
new[]
{
scope.RuntimeType
scope.RuntimeType
},
source);
}
Expand All @@ -109,7 +131,7 @@ private static Expression ToSet(
ConstructorInfo? ctor =
typedGeneric.GetConstructor(new[]
{
source.Type
source.Type
});

if (ctor is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ protected override ISelectionVisitorAction VisitObjectType(

return base.VisitObjectType(field, objectType, selectionSet, context);
}

public static readonly QueryableProjectionVisitor Default = new();
}
Loading

0 comments on commit 3217ebd

Please sign in to comment.