Call function in dynamic linq

2020-02-26 10:40发布

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)");

7条回答
成全新的幸福
2楼-- · 2020-02-26 10:55

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));
查看更多
够拽才男人
3楼-- · 2020-02-26 11:00

@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.

查看更多
We Are One
4楼-- · 2020-02-26 11:03

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".

查看更多
走好不送
5楼-- · 2020-02-26 11:09

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.

查看更多
祖国的老花朵
6楼-- · 2020-02-26 11:18

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);
查看更多
老娘就宠你
7楼-- · 2020-02-26 11:19

@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)");
查看更多
登录 后发表回答