/// <summary> /// Given a syntax of { fields, to, selection, from, object } with a context /// it will build the correct select statement /// </summary> /// <param name="name"></param> /// <param name="context"></param> /// <param name="selectContext"></param> /// <returns></returns> private IGraphQLBaseNode BuildDynamicSelectForObjectGraph(ExpressionResult selectFromExp, ParameterExpression selectFromParam, string name, EntityGraphQLParser.ObjectSelectionContext context) { try { // visit child fields. Will be field or entityQueries again // These expression will be built on the element type var oldContext = currentExpressionContext; currentExpressionContext = selectFromExp; var fieldExpressions = context.children.Select(c => Visit(c)).Where(n => n != null).ToList(); currentExpressionContext = oldContext; var graphQLNode = new GraphQLQueryNode(schemaProvider, fragments, name, selectFromExp, selectFromParam, fieldExpressions, selectFromExp); return(graphQLNode); } catch (EntityGraphQLCompilerException ex) { throw SchemaException.MakeFieldCompileError($"Failed compiling field {name}", ex.Message); } }
/// <summary> /// Build a Select() on the context of the field type /// </summary> /// <param name="context"></param> /// <returns></returns> public IGraphQLBaseNode ParseFieldSelect(ExpressionResult expContext, string name, EntityGraphQLParser.ObjectSelectionContext context) { try { IGraphQLBaseNode graphQLNode = null; if (expContext.Type.IsEnumerableOrArray()) { graphQLNode = BuildDynamicSelectOnCollection(expContext, name, context); } else { // Could be a list.First() that we need to turn into a select, or // other levels are object selection. e.g. from the top level people query I am selecting all their children { field1, etc. } // Can we turn a list.First() into and list.Select().First() var listExp = ExpressionUtil.FindIEnumerable(expContext); if (listExp.Item1 != null) { // yes we can // rebuild the ExpressionResult so we keep any ConstantParameters var item1 = (ExpressionResult)listExp.Item1; item1.AddConstantParameters(expContext.ConstantParameters); item1.AddServices(expContext.Services); graphQLNode = BuildDynamicSelectOnCollection(item1, name, context); graphQLNode.SetCombineExpression(listExp.Item2); } else { graphQLNode = BuildDynamicSelectForObjectGraph(expContext, currentExpressionContext.AsParameter(), name, context); } } return(graphQLNode); } catch (EntityGraphQLCompilerException ex) { throw SchemaException.MakeFieldCompileError($"Error compiling field {name}", ex.Message); } }
/// Given a syntax of someCollection { fields, to, selection, from, object } /// it will build a select assuming 'someCollection' is an IEnumerables private IGraphQLBaseNode BuildDynamicSelectOnCollection(ExpressionResult queryResult, string resultName, EntityGraphQLParser.ObjectSelectionContext context) { var elementType = queryResult.Type.GetEnumerableOrArrayType(); var contextParameter = Expression.Parameter(elementType, $"p_{elementType.Name}"); var exp = queryResult; var oldContext = currentExpressionContext; currentExpressionContext = (ExpressionResult)contextParameter; // visit child fields. Will be more fields var fieldExpressions = context.children.Select(c => Visit(c)).Where(n => n != null).ToList(); var gqlNode = new GraphQLQueryNode(schemaProvider, fragments, resultName, exp, oldContext.AsParameter(), fieldExpressions, (ExpressionResult)contextParameter); currentExpressionContext = oldContext; return(gqlNode); }
/// <summary> /// Given a syntax of { fields, to, selection, from, object } with a context /// it will build the correct select statement /// </summary> /// <param name="name"></param> /// <param name="context"></param> /// <param name="selectContext"></param> /// <returns></returns> private IGraphQLBaseNode BuildDynamicSelectForObjectGraph(ExpressionResult selectFromExp, ParameterExpression selectFromParam, string name, EntityGraphQLParser.ObjectSelectionContext context) { try { // visit child fields. Will be field or entityQueries again // These expression will be built on the element type var oldContext = currentExpressionContext; currentExpressionContext = selectFromExp; ParameterExpression replacementParameter = null; // we might be using a service i.e. ctx => WithService((T r) => r.DoSomething(ctx.Entities.Select(f => f.Id).ToList())) // if we can we want to avoid calling that multiple times with a expression like // r.DoSomething(ctx.Entities.Select(f => f.Id).ToList()) == null ? null : new { // Field = r.DoSomething(ctx.Entities.Select(f => f.Id).ToList()).Blah // } // by wrapping the whole thing in a method that does the null check once. // This means we build the fieldExpressions on a parameter of the result type bool wrapField = currentExpressionContext.Services.Any(); if (wrapField) { // replace with a parameter. The expression is compiled at execution time once replacementParameter = Expression.Parameter(selectFromExp.Type, "null_wrap"); currentExpressionContext = (ExpressionResult)replacementParameter; } var fieldExpressions = context.children.Select(c => Visit(c)).Where(n => n != null).ToList(); currentExpressionContext = oldContext; var graphQLNode = new GraphQLQueryNode(schemaProvider, fragments, name, selectFromExp, selectFromParam, fieldExpressions, selectFromExp) { IsWrapped = wrapField }; return(graphQLNode); } catch (EntityGraphQLCompilerException ex) { throw SchemaException.MakeFieldCompileError($"Failed compiling field {name}", ex.Message); } }