Call function in dynamic linq

2020-02-26 10:52发布

问题:

I'm trying to call a function in a dynamic linq select statement, but im getting error:

No property or field 'A' exists in type 'Tuple2'

Example code:

void Main()
{
    var a = new Tuple<int, int>(1,1);
    var b = new[]{ a };
    var q = b.AsQueryable().Select("A.Test(it.Item1)");

    q.Dump();
}

public static class A
{
    public static int Test(int i)
    {
        return i++;
    }
}

How should I change my code to get this working?

If I call built in function Convert.ToInt32 for example it works fine.

var q = b.AsQueryable().Select("Convert.ToInt32(it.Item1)");

Also how do I cast a property using dynamic linq?

var q = b.AsQueryable().Select("((float)it.Item1)");

回答1:

I'll say that the dynamic-linq isn't "strong enough" to do these things. It looks for methods only in the given objects and some special classes: Math, Convert, the various base types (int, float, string, ...), Guid, Timespan, DateTime

The list of these types is clearly visible if you use ilspy/reflector on the file. They are in System.Linq.Dynamic.ExpressionParser.predefinedTypes .

Now, clearly I could be wrong, but this works: .Select("Guid.NewGuid().ToString()").Cast<string>().ToArray()

showing that it's quite probable that that is the "correct" list.

There is an article here on how to modify Dynamic LINQ if you are interested http://www.krizzcode.com/2012/01/extending-dynamiclinq-language.html

Now, an intelligent man would take the source of dynamic linq and simply expand that array... But here there aren't intelligent men... There are only programmers that want blood! Blood but especially innards!

var type = typeof(DynamicQueryable).Assembly.GetType("System.Linq.Dynamic.ExpressionParser");

FieldInfo field = type.GetField("predefinedTypes", BindingFlags.Static | BindingFlags.NonPublic);

Type[] predefinedTypes = (Type[])field.GetValue(null);

Array.Resize(ref predefinedTypes, predefinedTypes.Length + 1);
predefinedTypes[predefinedTypes.Length - 1] = typeof(A); // Your type

field.SetValue(null, predefinedTypes);

Do this (with the types you want) BEFORE the first call to Dynamic Linq (because after the first call the methods/properties of these types are cached)

Explanation: we use reflection to add our object(s) to this "special list".



回答2:

I know there is already an accepted answer on this but it did not work for me. I am using Dynamic Linq 1.1.4. I wanted to do a query like this

$.GetNewestRisk() == null

Where GetNewestRisk() is a public method on the object represented by $. I kept getting this error "Error running query, Methods on type 'Patient' are not accessible (at index 2)".

I found in the source code there is a GlobalConfig object that allows a custom provider to be assigned which will hold all of the types you may want to work with. Here is the source code for the custom provider:

public class CustomTypeProvider: IDynamicLinkCustomTypeProvider
{
    public HashSet<Type> GetCustomTypes()
    {
        HashSet<Type> types = new HashSet<Type>();
        types.Add(typeof(Patient));
        types.Add(typeof(RiskFactorResult));
        types.Add(typeof(PatientLabResult));
        types.Add(typeof(PatientVital));
        return types;
    }
}

Here is how I am using it:

System.Linq.Dynamic.GlobalConfig.CustomTypeProvider = new CustomType();

After making this call I am able to call methods on the objects inside of the expression.



回答3:

@xanatos answer doesn't work for .Net Core version. So I've found something similar related by @Kent on the System.Dynamic.Linq.Core tests DynamicExpressionParserTests written by the library's author himself.

The given TestCustomTypeProviderClass allows you to use the DynamicLinqType class annotation which is pretty usefull for this problem.

To answer to question, you then just needed to defined the class (ensure to annotate with DynamicLinqType) :

[DynamicLinqType] 
public static class A
{
   public static int Test(int i)
   {
      return i++;
   }
}

Add a customTypeProvider as mentioned above :

private class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider
{
   private HashSet<Type> _customTypes;

   public virtual HashSet<Type> GetCustomTypes()
   {
      if (_customTypes != null)
      {
          return _customTypes;
      }

      _customTypes = new HashSet<Type>(FindTypesMarkedWithDynamicLinqTypeAttribute(new[] { GetType().GetTypeInfo().Assembly }));
            return _customTypes;
    }
}

and use a ParsingConfig with the configurable Select to call it :

var config = new ParsingConfig
{
     CustomTypeProvider = new TestCustomTypeProvider()
};

var q = b.AsQueryable().Select(config, "A.Test(it.Item1)");


回答4:

@Armand has put together a brilliant solution for this issue, and being the only solution I was able to find regarding this I want to add to it for anyone who tries the same approach.

The class that is marked with...

[DynamicLinqType] 

... must be taken into consideration when you run the following line:

FindTypesMarkedWithDynamicLinqTypeAttribute(new[] { GetType().GetTypeInfo().Assembly })

In the solution provided above, this assumes the class that contains the function to be evaluated is on the same class the code currently resides in. If the methods are to be used outside of said class, the assembly will need to change.

FindTypesMarkedWithDynamicLinqTypeAttribute(new[] { typeof(AnotherClassName).Assembly })

Nothing changes from the solution above, this is just for clarification for anyone attempting to use it.



回答5:

I may be confused but your syntax whereby you are using a string in your Selects doesn't compile for me. The following syntax works:

var q = b.AsQueryable().Select(it => A.Test(it.Item1));


回答6:

var b = new[]{ a };

The above array is don't know what type of array , and it's not type safe ?

Your values are assigned in variant data type so it's not integer value (I think string value) ,when you get this values in your query must need to convert.toint32() because your class parameter data type is integer

Please try it

 var b = new **int**[]{ a }; 

instead of var b = new[]{ a };

The important hint is here (in bold):

No property or field 'xxx' exists in **type** 'xxx'

And Please look this for previous discussion :

Dynamic Linq - no property or field exists in type 'datarow'



回答7:

The following works for me:

var a = new Tuple<int, int>(1, 1);
var b = new[] { a };
var q = b.AsQueryable().Select(it=>A.Test(it.Item1));
var q1 = b.AsQueryable().Select(it => Convert.ToInt32(it.Item1));
var q2 = b.AsQueryable().Select(it => (float) it.Item1);