Пример #1
0
        // This takes a Queryable<T> as an object, and executes a T-typed method call against it
        // That is, you define a `call` in terms of Queryable<object> and it will be converted to Queryable<T>
        //
        // ex:  GenericQueryableCall(queryableAsObj, q => q.FirstOrDefault());
        //
        // In this case, the generic method is represented at compiled time as FirstOrDefault<object>.
        // However, the generic type is replaced at runtime to FirstOrDefault<T> then dynamically invoked on `queryable`
        //
        // This is necessary for NHibernate, which inspects the generic type of the method executing the queryable.
        // So, if the queryable is of compile-time type IQueryable<object> instead of IQueryable<TEntity>, NHibernate will fail on execution
        private static TReturn GenericQueryableCall <TReturn>(object queryable, Expression <Func <IQueryable <object>, TReturn> > call)
        {
            var entityType    = queryable.GetType().GetGenericArguments()[0];
            var mce           = call.Body as MethodCallExpression;
            var genericMethod = mce.Method.GetGenericMethodDefinition();

            var newMethod    = genericMethod.MakeGenericMethod(entityType);
            var newParameter = Expression.Parameter(typeof(IQueryable <>).MakeGenericType(entityType));
            var newArguments = mce.Arguments.Select(a => ParameterReplacer.Replace(a, call.Parameters[0], newParameter));
            var newCall      = Expression.Call(newMethod, newArguments);
            var newLambda    = Expression.Lambda(newCall, newParameter);

            return((TReturn)newLambda.Compile().DynamicInvoke(queryable));
        }
Пример #2
0
        private static MemberBinding GetBinding(FieldMap map, Type toType, Expression baseBindingExpr, int n)
        {
            var mapFieldName    = $"Field{n}";
            var toMember        = toType.GetMember(mapFieldName)[0];
            var expr            = map.SchemaField.GetExpression(new List <Input>()); // TODO: real inputs
            var replacedBase    = ParameterReplacer.Replace(expr.Body, expr.Parameters[1], baseBindingExpr);
            var replacedContext = ParameterReplacer.Replace(replacedBase, expr.Parameters[0], GraphQLSchema <TContext> .DbParam);

            if (!map.Children.Any())
            {
                return(Expression.Bind(toMember, replacedContext));
            }

            var memberInit = GetMemberInit(map.Children, replacedContext);

            return(Expression.Bind(toType.GetMember(mapFieldName)[0], memberInit));
        }
Пример #3
0
        public static IDictionary <string, object> Execute <TArgs, TEntity>(TContext context, GraphQLQuery <TContext, TArgs, TEntity> gqlQuery, Query query)
        {
            var args = TypeHelpers.GetArgs <TArgs>(query.Inputs);
            var queryableFuncExpr = gqlQuery.QueryableExprGetter(args);
            var replaced          = (Expression <Func <TContext, IQueryable <TEntity> > >)ParameterReplacer.Replace(queryableFuncExpr, queryableFuncExpr.Parameters[0], GraphQLSchema <TContext> .DbParam);
            var fieldMaps         = query.Fields.Select(f => MapField(f, gqlQuery.Type)).ToList();
            var selector          = GetSelector <TEntity>(fieldMaps);

            var selectorExpr = Expression.Quote(selector);
            var call         = Expression.Call(typeof(Queryable), "Select", new[] { typeof(TEntity), typeof(GQLQueryObject20) }, replaced.Body, selectorExpr);
            var expr         = (Expression <Func <TContext, IQueryable <GQLQueryObject20> > >)Expression.Lambda(call, GraphQLSchema <TContext> .DbParam);
            var transformed  = expr.Compile()(context);

            object results;

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

            case ResolutionType.ToList:
                results = transformed.ToList().Select(o => MapResults(o, fieldMaps));
                break;

            case ResolutionType.FirstOrDefault:
                results = MapResults(transformed.FirstOrDefault(), fieldMaps);
                break;

            case ResolutionType.First:
                results = MapResults(transformed.First(), fieldMaps);
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            return(new Dictionary <string, object> {
                { "data", results }
            });
        }
Пример #4
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));
        }
Пример #5
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));
            }
        }