Функция вызова в динамическом linq

Я пытаюсь вызвать функцию в операторе динамического выбора 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)");

person Magnus    schedule 19.08.2013    source источник
comment
Какой синтаксис, когда вы используете строку в методе Enumerable.Select?   -  person Bob.    schedule 19.08.2013
comment
@Боб. Написано в тегах: dynamic-linq   -  person xanatos    schedule 19.08.2013


Ответы (7)


Я скажу, что dynamic-linq недостаточно силен, чтобы делать такие вещи. Он ищет методы только в заданных объектах и ​​некоторых специальных классах: Math, Convert, различных базовых типах (int, float, string, ...), Guid, Timespan, DateTime

Список этих типов хорошо виден, если вы используете 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

Так вот, умный человек взял бы источник динамического линка и просто расширил бы этот массив... Но здесь нет умных людей... Есть только программисты, которые хотят крови! Кровь, но особенно внутренности!

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

Сделайте это (с нужными вам типами) ПЕРЕД первым вызовом Dynamic Linq (поскольку после первого вызова методы/свойства этих типов кэшируются)

Объяснение: мы используем отражение, чтобы добавить наши объекты в этот «специальный список».

person xanatos    schedule 19.08.2013
comment
@andy Ответ, что это невозможно, все еще является ответом - person xanatos; 19.08.2013
comment
Если, как и я, вы хотите снести кеш на случай, если вы захотите возиться с внутренностями в любое время, вы можете пометить это в конце приведенного выше кода: field = type.GetField("keywords", BindingFlags.Static | BindingFlags.NonPublic); field.SetValue(null, null); - person James Thorpe; 06.04.2017

Я знаю, что на этот вопрос уже есть принятый ответ, но у меня это не сработало. Я использую Dynamic Linq 1.1.4. Я хотел сделать такой запрос

$.GetNewestRisk() == null

Где GetNewestRisk() — общедоступный метод для объекта, представленного $. Я продолжал получать эту ошибку «Ошибка выполнения запроса, методы типа «Пациент» недоступны (по индексу 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();

После выполнения этого вызова я могу вызывать методы для объектов внутри выражения.

person Kywillis    schedule 15.12.2015
comment
Мне удалось адаптировать этот ответ к System.Linq.Dynamic.Core. Он имеет тот же интерфейс, но также имеет ParsingConfig, который позволяет вам предоставлять типы для отдельных выражений, а не применять пользовательские типы глобально. - person Kent; 04.03.2018

Ответ @xanatos не работает для версии .Net Core. Итак, я нашел нечто подобное, связанное с @Kent в тестах System.Dynamic.Linq.Core DynamicExpressionParserTests написаны самим автором библиотеки.

Данный TestCustomTypeProviderClass позволяет вам использовать аннотацию класса 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;
    }
}

и используйте ParsingConfig с настраиваемый Select для его вызова:

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

var q = b.AsQueryable().Select(config, "A.Test(it.Item1)");
person Armand    schedule 30.05.2018
comment
Взгляните на дополнительный ответ Криса Фрахера, если вы определяете свою функцию в другой сборке. - person Armand; 06.08.2018
comment
Теперь он включен в качестве пользовательского парсера по умолчанию. Так что, если ваши классы находятся в одной сборке, вам просто нужно их декорировать, никакой дополнительной настройки не требуется. - person Jereme Guenther; 19.04.2019

@Armand нашел блестящее решение этой проблемы, и, поскольку это единственное решение, которое я смог найти, я хочу добавить его для всех, кто пробует тот же подход.

Класс, отмеченный...

[DynamicLinqType] 

... необходимо учитывать при выполнении следующей строки:

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

В приведенном выше решении предполагается, что класс, содержащий оцениваемую функцию, находится в том же классе, в котором в настоящее время находится код. Если методы должны использоваться вне указанного класса, сборку необходимо будет изменить.

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

Ничего не меняется по сравнению с приведенным выше решением, это просто для пояснения для тех, кто пытается его использовать.

person Chris Fraher    schedule 16.07.2018

Я могу быть сбит с толку, но ваш синтаксис, в котором вы используете строку в своих Selects, не компилируется для меня. Работает следующий синтаксис:

var q = b.AsQueryable().Select(it => A.Test(it.Item1));
person dav_i    schedule 19.08.2013
comment
your Selects doesn't compile for me потому что OP использует динамический язык weblogs.asp.net/scottgu/archive/2008/01/07/ - person I4V; 19.08.2013
comment
А, значит, я запутался :) - person dav_i; 19.08.2013

var b = new[]{ a };

Приведенный выше массив не знает, какой тип массива, и он не является безопасным для типов?

Ваши значения присваиваются в вариантном типе данных, поэтому это не целочисленное значение (я думаю, строковое значение), когда вы получаете эти значения в своем запросе, необходимо преобразовать.toint32() потому что ваш тип данных параметра класса является целым числом

Пожалуйста, попробуйте

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

вместо var b = new[]{ a };

Важная подсказка здесь (выделено жирным шрифтом):

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

И, пожалуйста, посмотрите это для предыдущего обсуждения:

Dynamic Linq - в типе "datarow" нет свойства или поля '

person Ramesh Rajendran    schedule 19.08.2013

Для меня работает следующее:

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);
person Xs10tial    schedule 19.08.2013