Exemple #1
0
        private static ConditionalExpression GetMemberInit(GraphQLSchema <TContext> schema, Type queryType, IEnumerable <ExecSelection <Info> > selectionsEnumerable, Expression baseBindingExpr, ExpressionOptions options)
        {
            // Avoid possible multiple enumeration of selections-enumerable
            var selections = selectionsEnumerable as IList <ExecSelection <Info> > ?? selectionsEnumerable.ToList();

            // The '__typename'-field selection has to be added for queries with type conditions
            var typeConditionButNoTypeNameSelection = selections.Any() &&
                                                      selections.Any(s => s.TypeCondition != null);

            // Any '__typename' selection have to be replaced by the '__typename' selection of the target type' '__typename'-field.
            var typeNameConditionHasToBeReplaced = selections.Any(s => s.Name == TypenameFieldSelector);

            // Remove all '__typename'-selections as well as duplicates in the selections caused by fragments type condition selections.
            selections = selections
                         .Where(s => s.Name != TypenameFieldSelector)
                         .GroupBy(s => s.Name)
                         .Select(g => g.First())
                         .ToList();

            var bindings = selections
                           .Where(m => !m.SchemaField.Field().IsPost)
                           .Select(map => GetBinding(schema, map, queryType, baseBindingExpr, options))
                           .ToList();

            // Add selection for '__typename'-field of the proper type
            if (typeConditionButNoTypeNameSelection || typeNameConditionHasToBeReplaced)
            {
                // Find the base types' `__typename` field
                var graphQlType = schema.GetGQLType(baseBindingExpr.Type);
                while (graphQlType?.BaseType != null)
                {
                    graphQlType = graphQlType.BaseType;
                }
                var typeNameField = graphQlType?.OwnFields.Find(f => f.Name == TypenameFieldSelector);
                if (typeNameField != null && !typeNameField.IsPost)
                {
                    var typeNameExecSelection = new ExecSelection <Info>(
                        new SchemaField(
                            schema.Adapter.QueryTypes[graphQlType?.Name],
                            typeNameField,
                            schema.Adapter),
                        new FSharpOption <string>(typeNameField?.Name),
                        null,
                        new WithSource <ExecArgument <Info> >[] { },
                        new WithSource <ExecDirective <Info> >[] { },
                        new WithSource <ExecSelection <Info> >[] { }
                        );
                    bindings = bindings.Concat(new[] { GetBinding(schema, typeNameExecSelection, queryType, baseBindingExpr, options) }).ToList();
                }
            }

            var memberInit = Expression.MemberInit(Expression.New(queryType), bindings);

            return(NullPropagate(baseBindingExpr, memberInit));
        }
Exemple #2
0
        private static ConditionalExpression GetMemberInit(GraphQLSchema <TContext> schema, GraphQLType graphQlType, IEnumerable <ExecSelection <Info> > selectionsEnumerable, Expression parameterExpression, ExpressionOptions options)
        {
            var queryType = graphQlType.QueryType;
            // Avoid possible multiple enumeration of selections-enumerable
            var selections = selectionsEnumerable as IList <ExecSelection <Info> > ?? selectionsEnumerable.ToList();

            // The '__typename'-field selection has to be added for queries with type conditions
            var typeConditionButNoTypeNameSelection = selections.Any() &&
                                                      selections.Any(s => s.TypeCondition != null);

            // Any '__typename' selection have to be replaced by the '__typename' selection of the target type' '__typename'-field.
            var typeNameConditionHasToBeReplaced = selections.Any(s => s.Name == "__typename");

            var bindingsWithPossibleDuplicates =
                selections.Where(s => !s.SchemaField.Info.Field.IsPost)
                .Where(s => s.Name != "__typename")
                .Select(s => GetBinding(schema, s, graphQlType, parameterExpression, options))
                .ToList();
            var uniqueBindingMemberNames = bindingsWithPossibleDuplicates.Select(b => b.Member.Name).Distinct();
            var bindings =
                uniqueBindingMemberNames.Select(name => bindingsWithPossibleDuplicates.First(b => b.Member.Name == name));

            // Add selection for '__typename'-field of the proper type
            if (typeConditionButNoTypeNameSelection || typeNameConditionHasToBeReplaced)
            {
                // Find the base types' `__typename` field
                var typeNameField = graphQlType?.Fields.Find(f => f.Name == "__typename");
                if (typeNameField != null && !typeNameField.IsPost)
                {
                    var typeNameExecSelection = new ExecSelection <Info>(
                        new SchemaField(
                            schema.Adapter.QueryTypes[graphQlType?.Name],
                            typeNameField,
                            schema.Adapter),
                        new FSharpOption <string>(typeNameField?.Name),
                        null,
                        new WithSource <ExecArgument <Info> >[] {},
                        new WithSource <ExecDirective <Info> >[] {},
                        new WithSource <ExecSelection <Info> >[] {}
                        );
                    bindings =
                        bindings
                        .Where(b => b.Member.Name != "__typename")
                        .Concat(new[]
                                { GetBinding(schema, typeNameExecSelection, graphQlType, parameterExpression, options) })
                        .ToList();
                }
            }

            var memberInit = Expression.MemberInit(Expression.New(queryType), bindings);

            return(NullPropagate(parameterExpression, memberInit));
        }
Exemple #3
0
        private static MemberBinding GetBinding(GraphQLSchema <TContext> schema, ExecSelection <Info> map, Type toType, Expression baseBindingExpr, ExpressionOptions options)
        {
            var field          = map.SchemaField.Field();
            var needsTypeCheck = baseBindingExpr.Type != field.DefiningType.CLRType;
            var toMember       = toType.GetProperty(map.SchemaField.FieldName);
            // expr is form of: (context, entity) => entity.Field
            var expr = field.GetExpression(map.Arguments.Values());

            // The field might be defined on sub-types of the specified type, so add a type cast if necessary.
            // If appropriate, `entity.Field` becomes `(entity as TFieldDefiningType).Field`
            var typeCastedBaseExpression = needsTypeCheck ? Expression.TypeAs(baseBindingExpr, field.DefiningType.CLRType) : baseBindingExpr;

            // Replace (entity) with baseBindingExpr, note expression is no longer a LambdaExpression
            // `(context, entity) => entity.Field` becomes `someOtherEntity.Entity.Field` where baseBindingExpr is `someOtherEntity.Entity`
            var replacedBase = ParameterReplacer.Replace(expr.Body, expr.Parameters[1], typeCastedBaseExpression);

            // If the entity has to be casted, add a type check:
            // `(someOtherEntity.Entity as TFieldDefininingType).Field` becomes `(someOtherEntity.Entity is TFieldDefininingType) ? (someOtherEntity.Entity as TFieldDefininingType).Field : null`
            if (needsTypeCheck && options.TypeCheckInheritance)
            {
                // The expression type has to be nullable
                if (replacedBase.Type.IsValueType)
                {
                    replacedBase = Expression.Convert(replacedBase, typeof(Nullable <>).MakeGenericType(replacedBase.Type));
                }

                var typeCheck  = Expression.TypeIs(baseBindingExpr, field.DefiningType.CLRType);
                var nullResult = Expression.Constant(null, replacedBase.Type);
                replacedBase = Expression.Condition(typeCheck, replacedBase, nullResult);
            }

            // This just makes sure that the (context) parameter is the same as the one used by the whole query
            var replacedContext = ParameterReplacer.Replace(replacedBase, expr.Parameters[0], GraphQLSchema <TContext> .DbParam);

            // If there aren't any children, then we can assume that this is a scalar entity and we don't have to map child fields
            if (!map.Selections.Any())
            {
                if (options.CastAssignment && expr.Body.Type != toMember.PropertyType)
                {
                    replacedContext = Expression.Convert(replacedContext, toMember.PropertyType);
                }
                return(Expression.Bind(toMember, replacedContext));
            }

            // If binding a single entity, just use the already built selector expression (replaced context)
            // Otherwise, if binding to a list, introduce a new parameter that will be used in a call to .Select
            var listParameter = Expression.Parameter
                                    (field.Type.CLRType, field.Type.CLRType.Name.Substring(0, 1).ToLower()); // TODO: Name conflicts in expressions?
            var bindChildrenTo = map.SchemaField.Field().IsList ? listParameter : replacedContext;

            // Now that we have our new binding parameter, build the tree for the rest of the children
            var memberInit = GetMemberInit(schema, field.Type.QueryType, map.Selections.Values(), bindChildrenTo, options);

            // For single entities, we're done and we can just bind to the memberInit expression
            if (!field.IsList)
            {
                return(Expression.Bind(toMember, memberInit));
            }

            // However for lists, we need to check for null and call .Select() and .ToList() first
            var selectLambda = Expression.Lambda(memberInit, listParameter);
            var call         = Expression.Call(typeof(Enumerable), "Select", new[] { field.Type.CLRType, field.Type.QueryType }, replacedContext, selectLambda);
            var toList       = Expression.Call(typeof(Enumerable), "ToList", new[] { field.Type.QueryType }, call);

            return(Expression.Bind(toMember, options.NullCheckLists ? (Expression)NullPropagate(replacedContext, toList) : toList));
        }
Exemple #4
0
        public static object Execute
            (GraphQLSchema <TContext> schema, TContext context, GraphQLField field, ExecSelection <Info> query)
        {
            var mutReturn = field.RunMutation(context, query.Arguments.Values());

            var queryableFuncExpr = field.GetExpression(query.Arguments.Values(), mutReturn);
            var replaced          = (LambdaExpression)ParameterReplacer.Replace(queryableFuncExpr, queryableFuncExpr.Parameters[0], GraphQLSchema <TContext> .DbParam);

            // sniff queryable provider to determine how selector should be built
            var dummyQuery        = replaced.Compile().DynamicInvoke(context, null);
            var queryType         = dummyQuery.GetType();
            var expressionOptions = schema.GetOptionsForQueryable(queryType);

            if (expressionOptions.UseBaseType)
            {
                queryType         = queryType.BaseType;
                expressionOptions = schema.GetOptionsForQueryable(queryType);
            }

            var queryExecSelections = query.Selections.Values();
            var selector            = GetSelector(schema, field.Type, queryExecSelections, expressionOptions);

            if (field.ResolutionType != ResolutionType.Unmodified)
            {
                var selectorExpr = Expression.Quote(selector);

                // TODO: This should be temporary - queryable and enumerable should both work
                var body = replaced.Body;
                if (body.NodeType == ExpressionType.Convert)
                {
                    body = ((UnaryExpression)body).Operand;
                }

                var call        = Expression.Call(typeof(Queryable), "Select", new[] { field.Type.CLRType, field.Type.QueryType }, body, selectorExpr);
                var expr        = Expression.Lambda(call, GraphQLSchema <TContext> .DbParam);
                var transformed = expr.Compile().DynamicInvoke(context);

                object results;
                switch (field.ResolutionType)
                {
                case ResolutionType.Unmodified:
                    throw new Exception("Queries cannot have unmodified resolution. May change in the future.");

                case ResolutionType.ToList:
                    var list = GenericQueryableCall <IEnumerable <object> >(transformed, q => q.ToList());
                    results = list.Select(o => MapResults(o, queryExecSelections, schema)).ToList();
                    break;

                case ResolutionType.FirstOrDefault:
                    var fod = GenericQueryableCall(transformed, q => q.FirstOrDefault());
                    results = MapResults(fod, queryExecSelections, schema);
                    break;

                case ResolutionType.First:
                    var first = GenericQueryableCall(transformed, q => q.FirstOrDefault());
                    results = MapResults(first, queryExecSelections, schema);
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }

                return(results);
            }
            else
            {
                var invocation = Expression.Invoke(selector, replaced.Body);
                var expr       = Expression.Lambda(invocation, GraphQLSchema <TContext> .DbParam);
                var result     = expr.Compile().DynamicInvoke(context);
                return(MapResults(result, queryExecSelections, schema));
            }
        }