System.Linq.Dynamic.Core icon indicating copy to clipboard operation
System.Linq.Dynamic.Core copied to clipboard

Call generic method failed (eg: EF shadow property `"EF.Property<string>(it, \"PropName\")"`)

Open neilbgr opened this issue 9 months ago • 2 comments

Hi,

I need to query an EF shadow property with DynamicLinq, so logicaly I do "EF.Property<string>(it, \"ShadowPropName\")". But this exception throws: No property or field 'Property' exists in type 'EF'. Of course, I added the EF static class with my CustomTypeProvider. In your source code, I found that generic methods are not processed. I'd try this code and it works fine:

public class ExpressionParser
{
    [...]

    private Expression ParseMemberAccess(Type? type, Expression? expression, string? id = null)
    {

        [...]

        if (_textParser.CurrentToken.Id == TokenId.OpenParen 
            || _textParser.CurrentToken.Id == TokenId.LessThan) // <-- +++++++ Detect generic arg types with "<"
        {
            Expression[]? args = null;

            var isStaticAccess = expression == null;
            var isConstantString = expression is ConstantExpression { Value: string };
            var isStringWithStringMethod = type == typeof(string) && _methodFinder.ContainsMethod(type, id, isStaticAccess);
            var isApplicableForEnumerable = !isStaticAccess && !isConstantString && !isStringWithStringMethod;

            if (isApplicableForEnumerable &&
                TypeHelper.TryFindGenericType(typeof(IEnumerable<>), type, out var enumerableType) &&
                TryParseEnumerable(expression!, enumerableType, id, type, out args, out var enumerableExpression))
            {
                return enumerableExpression;
            }

            // +++++++++++++++++++++++++++++++++++++++++
            List<Type> typeArguments = new();
            if (_textParser.CurrentToken.Id == TokenId.LessThan)
            {
                _textParser.NextToken(); // Skip "<"
                do
                {
                    if (_textParser.CurrentToken.Id == TokenId.Comma)
                    {
                        _textParser.NextToken(); // Skip ","
                    }
                    if (_textParser.CurrentToken.Id == TokenId.Identifier)
                    {
                        string typeName = GetIdentifier();
                        Type typeArgument = ResolveTypeStringFromArgument(typeName);
                        typeArguments.Add(typeArgument);
                    }
                    _textParser.NextToken(); // Skip the type from identifier
                }
                while (_textParser.CurrentToken.Id != TokenId.GreaterThan);
                _textParser.NextToken(); // Skip ">". The next tkoen would be "("
            }
            // +++++++++++++++++++++++++++++++++++++++++

            // If args is not set by TryParseEnumerable (in case when the expression is not an Enumerable), do parse the argument list here.            
            args ??= ParseArgumentList();
            switch (_methodFinder.FindMethod(type, id, isStaticAccess, ref expression, ref args, out var methodBase))
            {
                case 0:
                    throw ParseError(errorPos, Res.NoApplicableMethod, id, TypeHelper.GetTypeName(type));

                case 1:
                    var method = (MethodInfo)methodBase!;
                    if (!PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.DeclaringType!) && !PredefinedMethodsHelper.IsPredefinedMethod(_parsingConfig, method))
                    {
                        throw ParseError(errorPos, Res.MethodIsInaccessible, id, TypeHelper.GetTypeName(method.DeclaringType!));
                    }

                    MethodInfo methodToCall;
                    if (!method.IsGenericMethod)
                    {
                        methodToCall = method;
                    }
                    else
                    {
                        // ----------- Removed original code
                        // +++++++++++ Simply make generic method with type list
                        methodToCall = method.MakeGenericMethod(typeArguments.ToArray());
                        // +++++++++++
                    }

                    return CallMethod(expression, methodToCall, args);

                default:
                    throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, TypeHelper.GetTypeName(type));
            }
        }

       [...]

        throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type));
    }

Perhaps there is a better solution?

Best regards

neilbgr avatar Mar 29 '25 21:03 neilbgr

@StefH , would you prefer me to create a PR?

neilbgr avatar Apr 01 '25 10:04 neilbgr

@neilbgr You can create a PR, however this looks like a small change which could have a lot of impact, so extended unit tests are also needed.

Maybe I can start the PR, and then let you review it?

(I don't know when I can work on this...)

StefH avatar Apr 06 '25 06:04 StefH