Dan
Dan

Reputation: 7724

GraphQL: Variable of type referenced from scope, but it is not defined

I am trying to retrieve the value of a property through an Expression. However, when I run the code, I get the exception

Unhandled exception. System.InvalidOperationException: variable 'teacher' of type 'GraphQlMcve.Program+Teacher' referenced from scope '', but it is not defined

This occurs in the method below when I try to compile the expression.

protected FieldBuilder<T, object> PupilListField(string name,
    Expression<Func<T, IReadOnlyCollection<Pupil>>> pupils)
{
    return BaseAugmentedPupilListQuery(name)
        .Resolve(context =>
        {
            IEnumerable<Pupil> pupilList =
                Expression.Lambda<Func<IReadOnlyCollection<Pupil>>>(pupils.Body).Compile()();
            return AugmentedPupilListQueryBaseResolver(context, pupilList);
        });
}

The expression I am using is teacher => teacher.Pupils. Why is this happening?

A runnable example is below.


The below code example uses the GraphQL NuGet package Install-Package GraphQL -Version 2.4.0.

using GraphQL.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using GraphQL;
using GraphQL.Types;

namespace GraphQlMcve
{
    internal class Program
    {
        private static void Main()
        {
            const string query = @"{ teachers { id, name, pupils(id: ""2"") { id, name } } }";
            Schema schema = new Schema { Query = new SchoolQuery() };
            Console.WriteLine(schema.Execute(_ => { _.Query = query; _.ExposeExceptions = true; _.ThrowOnUnhandledException = true; }));
        }

        private class Pupil
        {
            public string Id { get; set; }
            public string Name { get; set; }
        }

        private class PupilType : ObjectGraphType
        {
            public PupilType()
            {
                Field<NonNullGraphType<IdGraphType>>(nameof(Pupil.Id));
                Field<StringGraphType>(nameof(Pupil.Name));
            }
        }

        private class Teacher
        {
            public string Id { get; set; }
            public string Name { get; set; }
            public List<Pupil> Pupils { get; set; }
        }

        private class TeacherType : BaseEntityGraphType<Teacher>
        {
            public TeacherType()
            {
                Field<NonNullGraphType<IdGraphType>>(nameof(Teacher.Id));
                Field<StringGraphType>(nameof(Teacher.Name));
                PupilListField(nameof(Teacher.Pupils), teacher => teacher.Pupils);
            }
        }

        private class SchoolQuery : BaseEntityGraphType
        {
            public SchoolQuery()
            {
                List<Pupil> pupils = new List<Pupil>
                {
                    new Pupil { Id = "1", Name = "Sarah" },
                    new Pupil { Id = "2", Name = "Adam" },
                    new Pupil { Id = "3", Name = "Gill" },
                };

                List<Teacher> teachers = new List<Teacher> { new Teacher { Id = "4", Name = "Sarah", Pupils = pupils} };

                PupilListField("pupils", pupils);

                Field<ListGraphType<TeacherType>>(
                    "teachers",
                    arguments: new QueryArguments(
                        new QueryArgument<IdGraphType> { Name = "id" }
                    ),
                    resolve: context => teachers
                );
            }
        }

        private abstract class BaseEntityGraphType<T> : ObjectGraphType<T>
        {
            protected FieldBuilder<T, object> PupilListField(string name,
                Expression<Func<T, IReadOnlyCollection<Pupil>>> pupils)
            {
                return BaseAugmentedPupilListQuery(name)
                    .Resolve(context =>
                    {
                        IEnumerable<Pupil> pupilList =
                            Expression.Lambda<Func<IReadOnlyCollection<Pupil>>>(pupils.Body).Compile()();
                        return AugmentedPupilListQueryBaseResolver(context, pupilList);
                    });
            }

            protected FieldBuilder<T, object> PupilListField(string name, IReadOnlyCollection<Pupil> pupils)
            {
                return BaseAugmentedPupilListQuery(name)
                    .Resolve(context => AugmentedPupilListQueryBaseResolver(context, pupils));
            }

            private FieldBuilder<T, object> BaseAugmentedPupilListQuery(string name)
            {
                return Field<ListGraphType<PupilType>>()
                    .Name(name)
                    .Description("")
                    .Argument<IdGraphType>("id", "");
            }

            private static IEnumerable<Pupil> AugmentedPupilListQueryBaseResolver(
                ResolveFieldContext<T> context,
                IEnumerable<Pupil> pupils)
            {
                string id = context.GetArgument<string>("id");
                return string.IsNullOrWhiteSpace(id) ? pupils : pupils.Where(pupil => pupil.Id == id);
            }
        }

        private abstract class BaseEntityGraphType : BaseEntityGraphType<object> { }
    }
}

Upvotes: 0

Views: 250

Answers (1)

Eldar
Eldar

Reputation: 10790

IEnumerable<Pupil> pupilList = Expression.Lambda<Func<IReadOnlyCollection<Pupil>>>(pupils.Body).Compile()();

This part of code is wrong. You pick a part of expression and you compile it. The part you compile have a teacher expression and you break the relation with it. What you can do is : Compile the main expression which is you already passed in your function.

var pupilList = puplis.Compile()(/* you need to pass here an actual object */); 

When you compile your expression you create a function which processes an object but you are not passing the object. In your teachers example it has to be a teacher object.

Upvotes: 1

Related Questions