Разрешение параметров LINQ, которые были переданы методу, в котором используется выражение LINQ.

Я работаю над транслятором LINQ to SQL. Он должен переводить запросы LINQ в SQL. Я сосредоточен на создании WHERE части запроса. Я просматриваю дерево выражений LINQ, а затем сталкиваюсь с проблемой, что не могу получить значение фактического параметра, переданного методу, содержащему вызов LINQ.

ПРИМЕЧАНИЕ. Я не использую ни LINQ to SQL, ни Entity Framework, ни NHibernate, потому что я просто не могу, поскольку в игре присутствует система VB6 Legacy.

Итак, чего я пытаюсь добиться, так это вызвать где-то на высоком уровне следующее:

int? parameterForCall = cmb.SelectedValue;

Затем у меня есть такой метод, и он находится в классе ExpenseDAL, который вызывает BaseDAL<Expense>.GetAll(X):

public IList<Expense> GetAll(int? parameterForCall)
{
    IList<Expense> expenses = BaseDAL<Expense>.GetAll(t => t.Fixed == 
                                                           parameterForCall);
}

GetAll имеет такую ​​сигнатуру:

public static IList<T> GetAll(Expression<Func<T, bool>> predicate = null);

Затем я вызываю метод GetCondition, который преобразует выражение в SQL:

private static string GetCondition(Expression predicate = null);

Это рекурсивная функция. В нем я попадаю в ситуацию, что мне нужно добраться до параметра, который я передал в выражение GetAll, с именем parameterForCall.

Проблема в том, что я могу написать это:

dynamic value = (predicate as ConstantExpression);

А в ImmediateWindow я вижу, что value.Value пишется вот так:

{FMC.Proxy.Common.BaseDAL.}
    parameterForCall: 19

Но я не могу получить значение 19. И я хочу, чтобы я мог преобразовать его в значение, чтобы поместить его в строку SQL.

Кто-нибудь знает, как получить значение 19, чтобы я мог использовать его в коде?


person gljivar    schedule 23.09.2011    source источник


Ответы (3)


Мой ответ предполагает - и ваш вопрос поддерживает это предположение - что предикат, переданный в GetCondition, имеет тип ConstantExpression.

Получить это значение непросто, потому что parameterForCall фиксируется в автоматически сгенерированном классе. Вы можете видеть это, когда смотрите на вывод (predicate as ConstantExpression).Value.GetType(). В моем случае это выводит:

UserQuery+<>c__DisplayClass0

Этот класс, в свою очередь, имеет общедоступное поле с именем parameterForCall. Теперь у вас есть две возможности получить это значение:

  1. Вы знаете имя этого поля, потому что вы управляете методом создания этого Выражения. В этом случае вы можете использовать этот код для получения значения:

    var constantExpression = (ConstantExpression)predicate;
    dynamic autoGeneratedClass = constantExpression.Value;
    object value = autoGeneratedClass.parameterForCall;
    

    value.GetType() вернет System.Int32, а value будет целым числом в штучной упаковке со значением 19.

  2. Вы не знаете названия этого поля. В таком случае сложнее. Вы можете использовать отражение, чтобы получить значение единственного общедоступного видимого поля, но если вы сделаете это, вы сделаете много предположений об автоматически сгенерированном классе.

person Daniel Hilgarth    schedule 23.09.2011
comment
Я хотел бы знать, как вы можете размышлять о autoGeneratedClass. Отражение динамических объектов отличается от статически типизированных объектов. - person Martin Liversage; 23.09.2011
comment
Вы не задумываетесь о autoGeneratedClass. Вы размышляете о constantExpression.Value, который является object. Если вы выберете вариант отражения 2, вам не понадобится autoGeneratedClass... - person Daniel Hilgarth; 23.09.2011
comment
@Martin: Ха-ха, ты обновил свой ответ с помощью моего комментария здесь? Вау, это просто дешево. Тем более, что вы пропустили предупреждение, которое я опубликовал об этом методе! - person Daniel Hilgarth; 24.09.2011
comment
@Martin: Мой последний комментарий не попал в нужный тон, и называть тебя дешевым было неправильно. Я сожалею об этом. Но: я намеренно не предоставил код для этого сценария, потому что хотел, чтобы ОП попросил об этом, если он действительно хочет пойти по этому пути. Почему? Потому что с моей стороны потребовались бы дополнительные исследования, чтобы лучше назвать возможные проблемы с этим подходом. В основном: (1) Повторное использование класса замыкания компилятором таким образом, что несколько переменных фиксируются в классе замыкания, и, таким образом, простое использование первого общедоступного поля может дать неверный результат. (2) Опираясь на детали реализации. - person Daniel Hilgarth; 25.09.2011
comment
Спасибо, мне пришлось изменить некоторые детали, но я на правильном пути. Кроме того, есть ли способ определить, генерируется ли value.Type динамически? Теперь я смотрю, начинается ли его имя с ‹›. var value = (предикат как ConstantExpression); динамическое оцениваемое значение = значение. значение; if (value.Type.Name.StartsWith(‹›)) { AssessmentValue = value.Type.GetFields()[0].GetValue(value.Value); } условие += GetSQLFormattedColumnValue((object)evaluatedValue); - person gljivar; 24.10.2011
comment
@gljivar: я не знаю ни одного... Зачем вам такая функциональность? - person Daniel Hilgarth; 24.10.2011

Вам просто нужно получить свойство из dynamic, которое у вас уже есть:

dynamic value = (predicate as ConstantExpression);
int? parameterForCall = value.parameterForCall;

Если вы не знаете имя параметра (и, возможно, тип), вы можете использовать отражение. Параметр, который вы ищете, существует как общедоступное поле объекта, возвращаемого ConstantExpression.Value. Это нигде не указано, поэтому используйте его на свой страх и риск.

Этот небольшой фрагмент кода демонстрирует, что:

class Expense { public int Fixed { get; set; } }

void Test(int? parameterForCall) {
  Expression<Func<Expense, bool>> predicate = t => t.Fixed == parameterForCall;
  var parameter = (
    (ConstantExpression) (
      (MemberExpression) (
        (BinaryExpression) predicate.Body
      ).Right
    ).Expression
  ).Value;
  var fieldInfo = parameter.GetType().GetFields().First();
  var name = fieldInfo.Name;
  var value = fieldInfo.GetValue(parameter);
  Console.WriteLine(name + " = " + value);
}

Если вы выполните Test(19), вывод будет parameterForCall = 19.

person Martin Liversage    schedule 23.09.2011

Надеюсь, этот ответ Stackoverflow поможет

деревья выражений linq получают значение параметра?

В основном вам, возможно, придется скомпилировать выражение, прежде чем вы сможете программно получить значение свойства.

Однако следите за штрафом за производительность. Удачи.

person Anas Karkoukli    schedule 23.09.2011