rivantsov / vita

VITA Application Framework
MIT License
59 stars 15 forks source link

[GraphQL] Invalid mapping expression for type VIQCore.Community.ICommunity->Community #167

Closed jasonlaw closed 3 years ago

jasonlaw commented 3 years ago

Hi @rivantsov ,

I am getting this error when starting up the GraphQL Server. Any idea?

================= GraphQL Model Errors Detected ========================= Invalid mapping expression for type VIQCore.Community.ICommunity->Community ================= End GraphQL Model Errors ==============================

  public class CommunityGraphQLModule : GraphQLModule
    {
        public CommunityGraphQLModule()
        {
            ObjectTypes.Add(typeof(Community), typeof(UploadFile));
            QueryType = typeof(ICommunityQuery);
            ResolverTypes.Add(typeof(CommunityResolvers));

            MapEntity<ICommunity>().To(x => x.ToModel());
        }
    } //class

// Entity
[Entity]
    [Display("{Id} - {Name}")]
    public interface ICommunity 
    {
        [PrimaryKey, Size(10)]
        [AutoSequenceNumber()]
        string Id { get; }

        [Size(300)]
        string Name { get; set; }

        [Nullable, Unlimited]
        string Description { get; set; }

        [Nullable, CleanupOnDelete]
        IFile Logo { get; set; }

        bool UseLogoAsBackgroundImage { get; set; }

        IList<IMemberInCommunity> RegisteredMembers { get; }

        IList<IVendorInCommunity> RegisteredVendors { get; }

        IList<IUser> Officers { get; }

        //[Nullable]
        //IBillingAccount BillingAccount { get; set; }
    }

  // Model
   public class Community
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public UploadFile Logo { get; set; }
        public bool UseLogoAsBackgroundImage { get; set; }
    }

       // Extensions
       public static Community ToModel(this ICommunity community)
        {
            return new Community
            {
                Id = community.Id,
                Name = community.Name,
                Description = community.Description,
                Logo = community.Logo.ToModel(),
                UseLogoAsBackgroundImage = community.UseLogoAsBackgroundImage
            };
        }

Full error:

NGraphQL.Server.ServerStartupException
  HResult=0x80131500
  Message=GraphQL Server startup failed. See details in  Errors property of this exception,  or in server.StartupErrors property or in Trace output.
  Source=NGraphQL.Server
  StackTrace:
   at NGraphQL.Server.GraphQLServer.Initialize()
   at NGraphQL.Server.AspNetCore.GraphQLHttpServer..ctor(GraphQLServer server, JsonSerializerSettings serializerSettings)
   at VIQCore.Http.VIQCoreHttpExtensions.CreateGraphQLHttpServer(GraphQLModule[] modules) in C:\JSL\VIQCore\2020Net\viqcore_net\ViqCore\Http\VIQCoreHttpExtensions.cs:line 109
   at VIQCore.Http.VIQCoreHttpExtensions.UseVIQCore(IApplicationBuilder app, GraphQLModule[] graphQLModules) in C:\JSL\VIQCore\2020Net\viqcore_net\ViqCore\Http\VIQCoreHttpExtensions.cs:line 78
   at VIQCore.Community.Startup.Configure(IApplicationBuilder app, IWebHostEnvironment env) in C:\JSL\VIQCore\2020Net\viqcore_net\VIQCore.Community\Startup.cs:line 45
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
   at Microsoft.AspNetCore.Hosting.ConfigureBuilder.<>c__DisplayClass4_0.<Build>b__0(IApplicationBuilder builder)
   at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass15_0.<UseStartup>b__1(IApplicationBuilder app)
   at Microsoft.AspNetCore.Mvc.Filters.MiddlewareFilterBuilderStartupFilter.<>c__DisplayClass0_0.<Configure>g__MiddlewareFilterBuilder|0(IApplicationBuilder builder)
   at Microsoft.AspNetCore.Server.IIS.Core.IISServerSetupFilter.<>c__DisplayClass2_0.<Configure>b__0(IApplicationBuilder app)
   at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder app)
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.<StartAsync>d__31.MoveNext()
rivantsov commented 3 years ago

Noooo, you can't do that. First you don't need any expr at all, just: MapEntity().To();

and that's it, since all types match. But if there's an expression (only if you have prop name mismatch, or need conversion) - then it should be individual assignments to properties. NGraphQL actually looks inside this expr new expr with multiple assignments, and takes individual assignment expressions and uses them; simple x => x.ToCommunity() is not what it expects; all prop assignments are hidden inside the method.

jasonlaw commented 3 years ago

I am not sure if I follow, but here is what I have changed to the code:

MapEntity<ICommunity>().To(x => new Community { Logo = x.Logo.ToModel() } );

Btw, in fact my entity and model are subclasses, above code was cleaned up to avoid any confusion, but it seems that the MapEntity doesn't support for the subclass.

Here is the new error I have got. ================= GraphQL Model Errors Detected ========================= Field 'Community.address' (module CommunityGraphQLModule) has no associated resolver or mapped entity field. Field 'Community.city' (module CommunityGraphQLModule) has no associated resolver or mapped entity field. Field 'Community.state' (module CommunityGraphQLModule) has no associated resolver or mapped entity field. Field 'Community.postcode' (module CommunityGraphQLModule) has no associated resolver or mapped entity field. Field 'Community.country' (module CommunityGraphQLModule) has no associated resolver or mapped entity field. ================= End GraphQL Model Errors ==============================

Entity with subclass

 public interface ILocation 
    {
        [Size(100)]
        string Address { get; set; }

        [Size(50)]
        string City { get; set; }

        [Size(50)]
        string State { get; set; }

        [Size(10)]
        string Postcode { get; set; }

        [Size(50)]
        string Country { get; set; }
    }

 [Entity]
    [Display("{Id} - {Name}")]
    public interface ICommunity : ILocation
    {
        [PrimaryKey, Size(10)]
        [AutoSequenceNumber()]
        string Id { get; }

        [Size(300)]
        string Name { get; set; }

        [Nullable, Unlimited]
        string Description { get; set; }

        [Nullable, CleanupOnDelete]
        IFile Logo { get; set; }

        bool UseLogoAsBackgroundImage { get; set; }

        IList<IMemberInCommunity> RegisteredMembers { get; }

        IList<IVendorInCommunity> RegisteredVendors { get; }

        IList<IUser> Officers { get; }

        //[Nullable]
        //IBillingAccount BillingAccount { get; set; }
    }    

Model with subclass

 public class Location  
    {
        public string Address { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Postcode { get; set; }
        public string Country { get; set; }

    }

    public class Community : Location
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public UploadFile Logo { get; set; }
        public bool UseLogoAsBackgroundImage { get; set; }
    }
jasonlaw commented 3 years ago

Did some debugging, could it be the Type.GetMembers doesn't return the base type's members?

NGraphQL.Server: ServerReflectionHelper.cs

 public static IList<MemberInfo> GetFieldsPropsMethods(this Type type, bool withMethods) {
      var mTypes = MemberTypes.Field | MemberTypes.Property;
      if (withMethods)
        mTypes |= MemberTypes.Method;
      var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
        .Where(m => !_specialMethods.Contains(m.Name))
        .Where(m => (m.MemberType & mTypes) != 0)
        .Where(m => !(m is MethodInfo mi && mi.IsSpecialName)) //filter out getters/setters
        .ToList();
      return members;
    }
rivantsov commented 3 years ago

Looking at this, seems like inside Vita there are also problems with derived interface entities

jasonlaw commented 3 years ago

Hmm...so far I don't have problem with Vita.

Btw, the problem seems only happens to Interface type, adding the fixed like below solve the problem.

  public static IList<MemberInfo> GetFieldsProps(this Type type) {
      if (type.IsInterface) {
        // https://stackoverflow.com/questions/358835/getproperties-to-return-all-properties-for-an-interface-inheritance-hierarchy
        return (new Type[] { type })
          .Concat(type.GetInterfaces())
          .SelectMany(i => i.GetFieldsPropsMethods(withMethods: false)).ToList();
      }
      return type.GetFieldsPropsMethods(withMethods: false); 
    }
rivantsov commented 3 years ago

yes, it is for derived interfaces only, fixing it. With Vita I found a case when derived interfaces fail too, outside GraphQL stuff. Fixing it.

rivantsov commented 3 years ago

pushed NGraphQL v1.1.1, should be fixed

jasonlaw commented 3 years ago

Tested OK, thanks!