// 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)); }
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)); }
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 } }); }
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)); }
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)); } }