在动态LINQ中调用函数

8

我试图在动态 linq 选择语句中调用一个函数,但是出现了错误:

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

示例代码:

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++;
    }
}

我该如何修改代码才能让它正常运行?

例如,如果我调用内置函数Convert.ToInt32,那么它就可以正常工作。

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

同时,我该如何使用动态LINQ转换属性?
var q = b.AsQueryable().Select("((float)it.Item1)");

你使用字符串在 Enumerable.Select 方法中的语法是什么? - Bob.
@Bob。标签中写着:动态LINQ。 - xanatos
8个回答

15

我认为 dynamic-linq 不能够很好地执行这些操作,它只会在给定的对象和一些特殊类(如MathConvert、各种基本类型(如intfloatstring等)、GuidTimespanDateTime)中查找方法。

如果使用 ilspy/reflector 查看文件,可以清楚地看到这些类型的列表,它们位于 System.Linq.Dynamic.ExpressionParser.predefinedTypes 中。

现在,我可能是错的,但是这个方法是有效的:.Select("Guid.NewGuid().ToString()").Cast<string>().ToArray()

这表明这很可能是“正确”的列表。

这里有一篇文章介绍如何修改 Dynamic LINQ,如果您感兴趣可以查看http://www.krizzcode.com/2012/01/extending-dynamiclinq-language.html

现在,一个聪明的人应该会取得 dynamic linq 的源码并扩展那个数组……但是这里没有聪明的人……只有想要的程序员!血液,尤其是内脏

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

在第一次调用动态Linq之前(因为第一次调用后这些类型的方法/属性会被缓存),使用所需的类型执行此操作。

解释:我们使用反射将对象添加到这个“特殊列表”中。


1
@andy 一个回答是不可能的,但这仍然是一个回答。 - xanatos
如果你和我一样,想在任何时候调整程序内部细节并清空缓存,可以将以下代码追加在上述代码结尾处:field = type.GetField("keywords", BindingFlags.Static | BindingFlags.NonPublic); field.SetValue(null, null); - James Thorpe

7

我知道已经有一个被接受的答案,但对我没有用。我正在使用Dynamic Linq 1.1.4。我想做一个类似这样的查询

$.GetNewestRisk() == null

GetNewestRisk()是由$表示的对象上的一个公共方法。我一直收到这个错误消息“运行查询时出错,类型'Patient'上的方法不可访问(索引为2)”。

我在源代码中发现了一个GlobalConfig对象,它允许分配自定义提供程序,该提供程序将保存您可能想要使用的所有类型。以下是自定义提供程序的源代码:

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;
    }
}

这是我使用它的方式:

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

调用此方法后,我就能够调用表达式中对象的方法。


3
我已经可以将这个答案适应到System.Linq.Dynamic.Core上了。它具有相同的接口,但还有ParsingConfig,允许您为单个表达式提供类型,而不是全局应用自定义类型。 - Kent

7
@xanatos的答案不适用于.Net Core版本。所以我在System.Dynamic.Linq.Core测试中找到了@Kent相关的类似内容,这是库作者本人编写的DynamicExpressionParserTests
给定的TestCustomTypeProvider类允许您使用DynamicLinqType类注释,这对于解决此问题非常有用。
要回答问题,您只需要定义类(确保注释为DynamicLinqType):
[DynamicLinqType] 
public static class A
{
   public static int Test(int i)
   {
      return i++;
   }
}

按照上述提到的方法添加customTypeProvider:

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;
    }
}

并使用带有可配置的Select的ParsingConfig来调用它:

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

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

如果您将函数定义到另一个程序集中,请查看Chris Fraher的其他答案。 - Armand
现在已将其包含为默认的自定义解析器。因此,如果您的类位于同一程序集中,您只需要对它们进行装饰,无需进行其他配置。 - Jereme

2
@Armand提供了一个很好的解决方案,这是我找到的唯一解决此问题的方法,我想为尝试相同方法的任何人添加一些内容。被标记为...的类...
[DynamicLinqType] 

在运行以下代码时,必须考虑到以下内容:

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

在上面提供的解决方案中,假设包含待评估函数的类与当前代码所在的类相同。如果要在该类外部使用这些方法,则需要改变程序集。
FindTypesMarkedWithDynamicLinqTypeAttribute(new[] { typeof(AnotherClassName).Assembly })

以上解决方案没有变化,这只是为那些试图使用它的人澄清一下。


0
关于当前版本(1.2.19)的动态LINQ,您可能会遇到另一个异常:
    System.Linq.Dynamic.Core.Exceptions.ParseException : Enum value 'Test' is not defined in enum type 'A'

为了让DLINQ知道你的类型'A',你有两个选项:

  1. 使用自定义类型提供程序设置解析配置,直接指定类型'A'。
  2. 使用属性[DynamicLinqType]标记你的类型。如果该类型已加载到当前域中(通常情况下是这样),你无需进行其他操作,因为默认的自定义类型提供程序已经扫描了当前AppDomain中标记为[DynamicLinqType]的类型。只有当该类型未加载到当前域中时,你需要像answer中所述那样进行操作。

如果你想同时使用这两种方法——第一种用于类型'A',第二种用于类型'B',那么你只需要将你的类型'A'与默认提供程序类型进行“合并”即可:

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;
        }
    }
}

-1

我可能有点困惑,但是你在 Select 中使用字符串的语法对我来说无法编译。以下语法可以正常工作:

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

1
你的选择对我来说无法编译,因为 OP 使用了 dynamic-linq。http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx - I4V
啊,原来我有点糊涂 :) - dav_i

-1

以下方法适用于我:

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

-1
var b = new[]{ a };

上述数组不知道是什么类型的数组,也不是类型安全的?
您的值被分配为变量数据类型,因此它不是整数值(我认为是字符串值),当您在查询中获取这些值时,必须进行转换为toint32(),因为您的类参数数据类型是整数。
请尝试一下。
 var b = new **int**[]{ a }; 

不要使用 var b = new[]{ a };

重要提示在这里(加粗):

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

请参考之前的讨论:

动态 Linq - 在 'datarow' 类型中不存在属性或字段


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接