Как зарегистрировать выражение предиката?

Обычно в моих репозиториях есть операторы регистрации для целей отладки, что позволяет мне видеть значения параметров. Недавно я пошел по пути создания универсального репозитория, который использует предикат Expression в качестве аргумента для большей гибкости, однако я не могу найти достойный способ регистрации условия там, где оно было бы удаленно полезно.

Пример метода:

public int GetCount<K>(Expression<Func<K, bool>> predicate) where K : class
{
    Logger.LogDebugMessage(String.Format("Parameters [predicate: {0}]", predicate == null ? string.Empty : predicate.Body.ToString()));

    ...
}

Вы можете видеть, что я использую Body.ToString() на этом этапе, но результаты не очень читабельны:

Parameters [predicate: (fa.SomeId == value(NameSpace.SomeClass+<>c__DisplayClass2).SomeId)]

В конечном счете, я бы хотел увидеть что-то похожее на следующее:

Parameters [predicate: (fa.SomeId == 1 && fa.Account.Name == "MyName").SomeId)]

По сути, значение этого журнала заключается в том, чтобы знать входные значения, когда что-то взрывается. Есть ли способ обойти это, за исключением того, что пользователь API должен предоставить предикат в виде строки?


person e36M3    schedule 03.03.2011    source источник
comment
Если вы хотите улучшить читаемость, это должно быть сделано за счет точности. В настоящее время предикат реагирует на изменения в классе замыкания. Ничто не мешает вам жадно вычислить правую часть выражения == и заменить ее своим значением, но это не будет правильно представлять фактический предикат.   -  person Ani    schedule 03.03.2011
comment
Можете ли вы указать мне пример? Кроме того, если я клонирую выражение только для целей регистрации, то, возможно, точность клонирования не имеет значения?   -  person e36M3    schedule 03.03.2011
comment
@ e36M3: Выражения, как правило, неизменяемы, так что речь не об этом. Если вы хотите пройти через это, вы можете создать новое выражение, чтобы выражение-член в правой части двоичного выражения было заменено выражением-константой. Затем вызовите ToString() для этого. Общий предикат-улучшитель будет намного сложнее написать, даже если вы уберете требование точности.   -  person Ani    schedule 03.03.2011
comment
@Ani, звучит как проигрышное предложение. Общий репозиторий как таковой чрезвычайно мощный, однако отсутствие хороших журналов кажется большим недостатком. Я смогу определить выражение, отследив код, но никогда не смогу узнать значения переменных через логи.   -  person e36M3    schedule 03.03.2011
comment
@ e36M3: Если бы вы более точно изложили требования, мы могли бы помочь больше. Будут ли все предикаты иметь вид x.Something == ExpressionNotInvolvingXYouWantToEvaluateEagerly? Если да, то легко упростить.   -  person Ani    schedule 03.03.2011
comment
@ Ани, на самом деле их не будет. Это типичные предикаты, которые будут переданы таким методам, как context.Something.First(predicate); Это действительно может быть что угодно, что относится к модели предметной области.   -  person e36M3    schedule 03.03.2011
comment
@e36M3: Тогда будет очень тяжело. Начните с более точного определения того, что вы хотите от упрощения. Вы хотите рекурсивно оценить все подвыражения, не включающие элемент ввода?   -  person Ani    schedule 03.03.2011
comment
@Ani, я добавил больше информации в исходный пост. Но да, по сути, я просто хочу, чтобы значения оценивались, поэтому int, string и т. д.   -  person e36M3    schedule 03.03.2011


Ответы (2)


Этот пост в блоге Мэтта Уоррена содержит код, который заменяет ссылку на локальную переменную именем переменной. Таким образом, вместо predicate: (fa.SomeId == value(NameSpace.SomeClass+<>c__DisplayClass2).SomeId) вы получите predicate: (fa.SomeId == someValue).

Сообщение в блоге:

http://blogs.msdn.com/b/mattwar/archive/2007/08/01/linq-building-an-iqueryable-provider-part-iii.aspx

Источник:

public static class Evaluator
{
    /// <summary>
    /// Performs evaluation & replacement of independent sub-trees
    /// </summary>
    /// <param name="expression">The root of the expression tree.</param>
    /// <param name="fnCanBeEvaluated">A function that decides whether a given expression node can be part of the local function.</param>
    /// <returns>A new tree with sub-trees evaluated and replaced.</returns>
    public static Expression PartialEval(Expression expression, Func<Expression, bool> fnCanBeEvaluated)
    {
        return new SubtreeEvaluator(new Nominator(fnCanBeEvaluated).Nominate(expression)).Eval(expression);
    }

    /// <summary>
    /// Performs evaluation & replacement of independent sub-trees
    /// </summary>
    /// <param name="expression">The root of the expression tree.</param>
    /// <returns>A new tree with sub-trees evaluated and replaced.</returns>
    public static Expression PartialEval(Expression expression)
    {
        return PartialEval(expression, Evaluator.CanBeEvaluatedLocally);
    }

    private static bool CanBeEvaluatedLocally(Expression expression)
    {
        return expression.NodeType != ExpressionType.Parameter;
    }

    /// <summary>
    /// Evaluates & replaces sub-trees when first candidate is reached (top-down)
    /// </summary>
    class SubtreeEvaluator : ExpressionVisitor
    {
        HashSet<Expression> candidates;

        internal SubtreeEvaluator(HashSet<Expression> candidates)
        {
            this.candidates = candidates;
        }

        internal Expression Eval(Expression exp)
        {
            return this.Visit(exp);
        }

        protected override Expression Visit(Expression exp)
        {
            if (exp == null)
            {
                return null;
            }
            if (this.candidates.Contains(exp))
            {
                return this.Evaluate(exp);
            }
            return base.Visit(exp);
        }

        private Expression Evaluate(Expression e)
        {
            if (e.NodeType == ExpressionType.Constant)
            {
                return e;
            }
            LambdaExpression lambda = Expression.Lambda(e);
            Delegate fn = lambda.Compile();
            return Expression.Constant(fn.DynamicInvoke(null), e.Type);
        }
    }

    /// <summary>
    /// Performs bottom-up analysis to determine which nodes can possibly
    /// be part of an evaluated sub-tree.
    /// </summary>
    class Nominator : ExpressionVisitor
    {
        Func<Expression, bool> fnCanBeEvaluated;
        HashSet<Expression> candidates;
        bool cannotBeEvaluated;

        internal Nominator(Func<Expression, bool> fnCanBeEvaluated)
        {
            this.fnCanBeEvaluated = fnCanBeEvaluated;
        }

        internal HashSet<Expression> Nominate(Expression expression)
        {
            this.candidates = new HashSet<Expression>();
            this.Visit(expression);
            return this.candidates;
        }

        protected override Expression Visit(Expression expression)
        {
            if (expression != null)
            {
                bool saveCannotBeEvaluated = this.cannotBeEvaluated;
                this.cannotBeEvaluated = false;
                base.Visit(expression);
                if (!this.cannotBeEvaluated)
                {
                    if (this.fnCanBeEvaluated(expression))
                    {
                        this.candidates.Add(expression);
                    }
                    else
                    {
                        this.cannotBeEvaluated = true;
                    }
                }
                this.cannotBeEvaluated |= saveCannotBeEvaluated;
            }
            return expression;
        }
    }
}
person smartcaveman    schedule 03.03.2011
comment
Исходный код класса ExpressionVisitor находится в конце этого сообщения: blogs.msdn.com/b/mattwar/archive/2007/07/31/ - person smartcaveman; 03.03.2011
comment
на самом деле важнее (для меня) знать значение переменной, а не имя. Например, если предположить, что у меня есть несколько вызовов метода репозитория, и один из них вызывает исключение, журнал должен сообщить мне, что fa.SomeId == 5 в случае исключения позволит мне быстро воспроизвести проблему. - person e36M3; 03.03.2011
comment
Опубликованный класс Evaluator делает именно это и инкапсулирует значение в ConstantExpression. Я уверен, что вы можете настроить его для любых других функций, которые вам нужны. - person smartcaveman; 03.03.2011
comment
Я обновил свой ответ, чтобы отразить, что это на самом деле функциональность класса. Я запутался. - person smartcaveman; 03.03.2011
comment
Это потрясающе. Большое Вам спасибо. - person e36M3; 03.03.2011
comment
Контекст кода, в котором я сейчас нахожусь, делает сложность этого слишком большой, чтобы ее можно было воспринять - мне было бы интересно увидеть быстрый пример того, как я использую этот код с учетом предиката. - person PandaWood; 22.11.2013
comment
Мои подсказки после выяснения этого: 1) вы должны использовать собственный класс ExpressionVisitor (не используйте тот, что в системе) 2) вам, вероятно, нужно будет решить, как обернуть ваш предикат в выражение, прежде чем вы сможете решить, как вызовите код здесь, т.е. Evaluator.PartialEval(expression); - см. stackoverflow.com/questions/3988560/ - person PandaWood; 22.11.2013

Однажды у меня была эта проблема, и я решил ее, создав перечисление для всех предикатов, которые можно было зарегистрировать, которых в моем случае было не так много. Если это не так в вашем случае, просто создайте одну запись перечисления для каждого предиката и Dictionary<Predicate<T>, YourEnum> и запишите значение перечисления вместо предиката.

person JBSnorro    schedule 03.03.2011
comment
К сожалению, значения могут быть бесконечными, например, столбцы идентификаторов и т. д. - person e36M3; 03.03.2011