src/Epam.GraphQL/Configuration/Implementations/FieldResolvers/EnumerableAsyncFuncResolverBase.cs (142 lines of code) (raw):
// Copyright © 2020 EPAM Systems, Inc. All Rights Reserved. All information contained herein is, and remains the
// property of EPAM Systems, Inc. and/or its suppliers and is protected by international intellectual
// property law. Dissemination of this information or reproduction of this material is strictly forbidden,
// unless prior written permission is obtained from EPAM Systems, Inc
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Epam.GraphQL.Extensions;
using Epam.GraphQL.Helpers;
using Epam.GraphQL.TaskBatcher;
using GraphQL;
using GraphQL.DataLoader;
using GraphQL.Resolvers;
namespace Epam.GraphQL.Configuration.Implementations.FieldResolvers
{
internal abstract class EnumerableAsyncFuncResolverBase<TThis, TEntity, TReturnType, TTransformedReturnType, TExecutionContext> :
IEnumerableResolver<TThis, TEntity, TReturnType, TExecutionContext>
{
public EnumerableAsyncFuncResolverBase(
string fieldName,
IProxyAccessor<TEntity, TExecutionContext> outerProxyAccessor,
IProxyAccessor<TReturnType, TTransformedReturnType, TExecutionContext> returnTypeProxyAccessor)
{
FieldName = fieldName;
OuterProxyAccessor = outerProxyAccessor;
ReturnTypeProxyAccessor = returnTypeProxyAccessor;
}
protected IProxyAccessor<TEntity, TExecutionContext> OuterProxyAccessor { get; }
protected IProxyAccessor<TReturnType, TTransformedReturnType, TExecutionContext> ReturnTypeProxyAccessor { get; }
protected string FieldName { get; }
protected abstract Func<IResolveFieldContext, IDataLoader<TEntity, IEnumerable<TTransformedReturnType>>> Resolver { get; }
protected abstract Func<IResolveFieldContext, IDataLoader<Proxy<TEntity>, IEnumerable<TTransformedReturnType>>> ProxiedResolver { get; }
public ValueTask<object?> ResolveAsync(IResolveFieldContext context)
{
Guards.AssertIfNull(context.Source);
return context.Source is Proxy<TEntity> proxy
? new ValueTask<object?>(ProxiedResolver(context).LoadAsync(proxy))
: new ValueTask<object?>(Resolver(context).LoadAsync((TEntity)context.Source));
}
public abstract IEnumerableResolver<TEntity, TSelectType, TExecutionContext> Select<TSelectType>(
Expression<Func<TReturnType, TSelectType>> selector,
IProxyAccessor<TSelectType, TExecutionContext>? selectTypeProxyAccessor);
public abstract TThis Where(Expression<Func<TReturnType, bool>> predicate);
public IFieldResolver SingleOrDefault()
{
return new AsyncFuncResolver<TEntity, TTransformedReturnType>(
ctx => Resolver(ctx)
.Then(items => items.SafeNull().SingleOrDefault()),
ctx => ProxiedResolver(ctx)
.Then(items => items.SafeNull().SingleOrDefault()));
}
public IFieldResolver FirstOrDefault()
{
return new AsyncFuncResolver<TEntity, TTransformedReturnType>(
ctx => Resolver(ctx)
.Then(items => items.SafeNull().FirstOrDefault()),
ctx => ProxiedResolver(ctx)
.Then(items => items.SafeNull().FirstOrDefault()));
}
public IEnumerableResolver<TEntity, TSelectType, TExecutionContext> Select<TSelectType>(Expression<Func<TEntity, TReturnType, TSelectType>> selector)
{
var factorizationResult = ExpressionHelpers.Factorize(selector);
AddMembers(factorizationResult);
var proxiedSelector = new Lazy<Func<Proxy<TEntity>, TTransformedReturnType, TSelectType>>(() =>
{
var outerProxyParam = Expression.Parameter(typeof(Proxy<TEntity>));
var innerProxyParam = Expression.Parameter(typeof(TTransformedReturnType));
var outers = factorizationResult.LeftExpressions
.Select(e => OuterProxyAccessor.Rewrite(e, e).CastFirstParamTo<Proxy<TEntity>>())
.Select(e => e.Body.ReplaceParameter(e.Parameters[0], outerProxyParam))
.ToList();
var inners = factorizationResult.RightExpressions
.Select(TransformInnerExpression)
.Select(e => e.Body.ReplaceParameter(e.Parameters[0], innerProxyParam))
.ToList();
var paramMap = new Dictionary<ParameterExpression, Expression>();
var parameters = factorizationResult.Expression.Parameters;
for (int i = 0; i < outers.Count; i++)
{
paramMap.Add(parameters[i], outers[i]);
}
for (int i = 0; i < inners.Count; i++)
{
paramMap.Add(parameters[i + outers.Count], inners[i]);
}
var exprBody = factorizationResult.Expression.Body.ReplaceParameters(paramMap);
var expr = Expression.Lambda<Func<Proxy<TEntity>, TTransformedReturnType, TSelectType>>(exprBody, outerProxyParam, innerProxyParam);
var compiledExpr = expr.Compile();
return compiledExpr;
});
var compiledSelector = new Lazy<Func<TEntity, TTransformedReturnType, TSelectType>>(() =>
{
var outerParam = Expression.Parameter(typeof(TEntity));
var innerProxyParam = Expression.Parameter(typeof(TTransformedReturnType));
var outers = factorizationResult.LeftExpressions
.Select(e => e.Body.ReplaceParameter(e.Parameters[0], outerParam))
.ToList();
var inners = factorizationResult.RightExpressions
.Select(TransformInnerExpression)
.Select(e => e.Body.ReplaceParameter(e.Parameters[0], innerProxyParam))
.ToList();
var paramMap = new Dictionary<ParameterExpression, Expression>();
var parameters = factorizationResult.Expression.Parameters;
for (int i = 0; i < outers.Count; i++)
{
paramMap.Add(parameters[i], outers[i]);
}
for (int i = 0; i < inners.Count; i++)
{
paramMap.Add(parameters[i + outers.Count], inners[i]);
}
var exprBody = factorizationResult.Expression.Body.ReplaceParameters(paramMap);
var expr = Expression.Lambda<Func<TEntity, TTransformedReturnType, TSelectType>>(exprBody, outerParam, innerProxyParam);
var compiledExpr = expr.Compile();
return compiledExpr;
});
return new EnumerableAsyncFuncResolver<TEntity, TSelectType, TExecutionContext>(
FieldName,
ctx => Resolver(ctx).Then(Continuation),
ctx => ProxiedResolver(ctx).Then(ProxiedContinuation),
OuterProxyAccessor);
IEnumerable<TSelectType> Continuation(TEntity source, IEnumerable<TTransformedReturnType> items)
{
return items.Select(item => compiledSelector.Value(source, item)).AsEnumerable();
}
IEnumerable<TSelectType> ProxiedContinuation(Proxy<TEntity> source, IEnumerable<TTransformedReturnType> items)
{
return items.Select(item => proxiedSelector.Value(source, item)).AsEnumerable();
}
}
protected LambdaExpression TransformInnerExpression(LambdaExpression expression)
{
return ReturnTypeProxyAccessor.Rewrite(expression, expression).CastFirstParamTo<TTransformedReturnType>();
}
protected void AddMembers(ExpressionFactorizationResult factorizationResult)
{
OuterProxyAccessor.AddMembers(FieldName, ReturnTypeProxyAccessor, factorizationResult);
}
protected ILoaderHooksExecuter<TTransformedReturnType>? CreateHooksExecuter(IResolveFieldContext context)
{
return ReturnTypeProxyAccessor.CreateHooksExecuter(context);
}
}
}