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