Пример #1
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));
        }
Пример #2
0
        private static LambdaExpression GetSelector(GraphQLSchema <TContext> schema, GraphQLType gqlType, IEnumerable <ExecSelection <Info> > selections, ExpressionOptions options)
        {
            var parameter = Expression.Parameter(gqlType.CLRType, "p");
            var init      = GetMemberInit(schema, gqlType.QueryType, selections, parameter, options);

            return(Expression.Lambda(init, parameter));
        }
Пример #3
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));
        }
Пример #4
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));
        }