Call function in dynamic linq
Asked Answered
S

8

9

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)");
Sarcenet answered 19/8, 2013 at 12:3 Comment(2)
What syntax is that where you use a string in the Enumerable.Select method?Sheikdom
@Sheikdom Written in the tags: dynamic-linqPatent
P
16

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

Patent answered 19/8, 2013 at 12:25 Comment(2)
@andy An answer that it isn't possible is still an answerPatent
If, like me, you want to blow away the cache in case you want to fiddle with the innards at any time, you can tag this on to the end of the above code: field = type.GetField("keywords", BindingFlags.Static | BindingFlags.NonPublic); field.SetValue(null, null);Arm
S
7

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.

Subsidiary answered 15/12, 2015 at 23:37 Comment(1)
I was able to adapt this answer to System.Linq.Dynamic.Core. It has the same interface but it also has ParsingConfig which allows you to supply types to individual expressions rather than applying the custom types globally.Superaltar
G
7

@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)");
Gecko answered 30/5, 2018 at 13:4 Comment(2)
Take a look at the Chris Fraher's additional answer if you define your function into another assembly.Gecko
This has now been included as the default custom parser. So if your classes are in the same assembly, you just have to decorate them, no additional config needed.Fiden
E
2

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

Execrate answered 16/7, 2018 at 22:38 Comment(0)
J
1

As regards the current version (1.2.19) of Dynamic LINQ, you will probably get another exception:

    System.Linq.Dynamic.Core.Exceptions.ParseException : Enum value 'Test' is not defined in enum type 'A'

To make DLINQ know your type 'A', you have two options:

  1. Set up parsing config with your own custom types provider where you directly specify the type 'A'.
  2. Mark your type with the attribute [DynamicLinqType]. If that type is loaded into the current domain (that's the usual case), you don't have to do anything more since the default custom type provider already scans the current AppDomain for types marked with [DynamicLinqType]. And only if that's not the case, i.e. your type is not loaded into the current domain, you have to do something like in that answer.

What if you would like to use both approaches - the first for type 'A' and the second for type 'B'? In that case, you just have to "merge" your type 'A' with the default provider types:

public class DynamicLinqTests
{
    [Test]
    public void Test()
    {
        var a = new Tuple<int, int>(1, 1);
        var b = new[] { a };

        var parsingConfig = new ParsingConfig
        {
            ResolveTypesBySimpleName = true,
            CustomTypeProvider = new TestCustomTypesProvider()
        };

        var queryWithA = b.AsQueryable().Select(parsingConfig, "A.Test(it.Item1)");
        queryWithA.ToDynamicList();

        var queryWithB = b.AsQueryable().Select(parsingConfig, "B.Test(it.Item1)");
        queryWithB.ToDynamicList();
    }

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

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

    public class TestCustomTypesProvider : DefaultDynamicLinqCustomTypeProvider
    {
        public override HashSet<Type> GetCustomTypes()
        {
            var customTypes = base.GetCustomTypes();
            customTypes.Add(typeof(A));
            return customTypes;
        }
    }
}
Jakob answered 27/7, 2022 at 20:20 Comment(0)
C
-1
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'

Contributor answered 19/8, 2013 at 12:17 Comment(0)
U
-1

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));
Uncrowned answered 19/8, 2013 at 12:18 Comment(2)
your Selects doesn't compile for me because OP uses dynamic-linq weblogs.asp.net/scottgu/archive/2008/01/07/…Designing
Ah so I was confused :)Uncrowned
D
-1

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);
Daina answered 19/8, 2013 at 12:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.