private static void AddFieldWithIdArgumentIfExists <TContextType>(SchemaProvider <TContextType> schema, Type contextType, Field fieldProp) { if (!fieldProp.Resolve.Type.IsEnumerableOrArray()) { return; } var schemaType = fieldProp.ReturnType.SchemaType; var idFieldDef = schemaType.GetFields().FirstOrDefault(f => f.Name == "id"); if (idFieldDef == null) { return; } // We need to build an anonymous type with id = RequiredField<idFieldDef.Resolve.Type>() // Resulting lambda is (a, p) => a.Where(b => b.Id == p.Id).First() // This allows us to "insert" .Select() (and .Include()) before the .First() var requiredFieldType = typeof(RequiredField <>).MakeGenericType(idFieldDef.Resolve.Type); var fieldNameAndType = new Dictionary <string, Type> { { "id", requiredFieldType } }; var argTypes = LinqRuntimeTypeBuilder.GetDynamicType(fieldNameAndType); var argTypesValue = argTypes.GetTypeInfo().GetConstructors()[0].Invoke(new Type[0]); var argTypeParam = Expression.Parameter(argTypes, $"args_{argTypes.Name}"); Type arrayContextType = schemaType.ContextType; var arrayContextParam = Expression.Parameter(arrayContextType, $"arrcxt_{arrayContextType.Name}"); var ctxId = Expression.PropertyOrField(arrayContextParam, "Id"); Expression argId = Expression.PropertyOrField(argTypeParam, "id"); argId = Expression.Property(argId, "Value"); // call RequiredField<>.Value to get the real type without a convert var idBody = Expression.MakeBinary(ExpressionType.Equal, ctxId, argId); var idLambda = Expression.Lambda(idBody, new[] { arrayContextParam }); Expression body = ExpressionUtil.MakeCallOnQueryable("Where", new Type[] { arrayContextType }, fieldProp.Resolve, idLambda); body = ExpressionUtil.MakeCallOnQueryable("FirstOrDefault", new Type[] { arrayContextType }, body); var contextParam = Expression.Parameter(contextType, $"cxt_{contextType.Name}"); var lambdaParams = new[] { contextParam, argTypeParam }; body = new ParameterReplacer().ReplaceByType(body, contextType, contextParam); var selectionExpression = Expression.Lambda(body, lambdaParams); var name = fieldProp.Name.Singularize(); if (name == null) { // If we can't singularize it just use the name plus something as GraphQL doesn't support field overloads name = $"{fieldProp.Name}ById"; } var field = new Field(schema, name, selectionExpression, $"Return a {fieldProp.ReturnType.SchemaType.Name} by its Id", argTypesValue, new GqlTypeInfo(fieldProp.ReturnType.SchemaTypeGetter, selectionExpression.Body.Type), fieldProp.AuthorizeClaims); schema.AddField(field); }
/// <summary> /// Given the type TContextType recursively create a query schema based on the public properties of the object. Schema is added into the provider schema /// </summary> /// <param name="schema">Schema tp add types to.</param> /// <param name="autoCreateIdArguments">If true (default), automatically create a field for any root array thats context object contains an Id property. I.e. If Actor has an Id property and the root TContextType contains IEnumerable<Actor> Actors. A root field Actor(id) will be created.</param> /// <param name="fieldNamer">Optionally provider a function to generate the GraphQL field name. By default this will make fields names that follow GQL style in lowerCaseCamelStyle</param> /// <typeparam name="TContextType"></typeparam> /// <returns></returns> public static SchemaProvider<TContextType> FromObject<TContextType>(SchemaProvider<TContextType> schema, bool autoCreateIdArguments = true, bool autoCreateEnumTypes = true, Func<MemberInfo, string> fieldNamer = null) { if (fieldNamer == null) fieldNamer = DefaultNamer; var contextType = typeof(TContextType); var rootFields = GetFieldsFromObject(contextType, schema, autoCreateEnumTypes, fieldNamer); foreach (var f in rootFields) { if (autoCreateIdArguments) { // add non-pural field with argument of ID AddFieldWithIdArgumentIfExists(schema, contextType, f); } schema.AddField(f); } return schema; }
/// <summary> /// Given the type TContextType recursively create a query schema based on the public properties of the object. /// </summary> /// <param name="autoCreateIdArguments">If True, automatically create a field for any root array thats context object contains an Id property. I.e. If Actor has an Id property and the root TContextType contains IEnumerable<Actor> Actors. A root field Actor(id) will be created.</param> /// <typeparam name="TContextType"></typeparam> /// <returns></returns> public static SchemaProvider <TContextType> FromObject <TContextType>(bool autoCreateIdArguments = true, bool autoCreateEnumTypes = true) { var schema = new SchemaProvider <TContextType>(); var contextType = typeof(TContextType); var rootFields = GetFieldsFromObject(contextType, schema, autoCreateEnumTypes); foreach (var f in rootFields) { if (autoCreateIdArguments) { // add non-pural field with argument of ID AddFieldWithIdArgumentIfExists(schema, contextType, f); } schema.AddField(f); } return(schema); }
/// <summary> /// Given the type TContextType recursively create a query schema based on the public properties of the object. /// </summary> /// <param name="autoCreateIdArguments">If true (default), automatically create a field for any root array thats context object contains an Id property. I.e. If Actor has an Id property and the root TContextType contains IEnumerable<Actor> Actors. A root field Actor(id) will be created.</param> /// <param name="autoCreateIdArguments">If true (default), automatically create ENUM types for enums found in the context object graph</param> /// <param name="fieldNamer">Optionally provider a function to generate the GraphQL field name. By default this will make fields names that follow GQL style in lowerCaseCamelStyle</param> /// <typeparam name="TContextType"></typeparam> /// <returns></returns> public static SchemaProvider <TContextType> FromObject <TContextType>(bool autoCreateIdArguments = true, bool autoCreateEnumTypes = true, Func <MemberInfo, string> fieldNamer = null) { var schema = new SchemaProvider <TContextType>(); return(FromObject(schema, autoCreateIdArguments, autoCreateEnumTypes, fieldNamer)); }