Strange Request. Print out Expression

Posts   
 
    
MarcoP avatar
MarcoP
User
Posts: 270
Joined: 29-Sep-2004
# Posted on: 18-Jan-2011 15:04:21   

I kind of have a strange request. The client of ours is asking to print out the search criteria for some of our reports. So, I use the predicate builder to create my expression and I was wondering if anyone knows how to traverse the expression and print out the expression. Kind of like:

CustomerName = 'Smith' LastName Like 'Doe%'

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39612
Joined: 17-Aug-2003
# Posted on: 18-Jan-2011 18:13:34   

Linq or our own query system?

Frans Bouma | Lead developer LLBLGen Pro
MarcoP avatar
MarcoP
User
Posts: 270
Joined: 29-Sep-2004
# Posted on: 18-Jan-2011 18:35:58   

Otis wrote:

Linq or our own query system?

I'm using Linq to LLBL and the version of the PredicateBuilder you wrote.

MTrinder
User
Posts: 1461
Joined: 08-Oct-2008
# Posted on: 18-Jan-2011 21:48:59   

I'm not sure, I'll bump it back to Frans for you.

Matt

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39612
Joined: 17-Aug-2003
# Posted on: 19-Jan-2011 09:12:00   

The only way I see how you can do this is by creating your own Expression visitor. But this will be complicated as a predicate expression can be deep and complex.

if he wants to see the queries executed, he can switch on tracing, see the queries, copy them to a text editor, switch off tracing and he has the queries. IMHO a much faster way to get this information.

Frans Bouma | Lead developer LLBLGen Pro
MarcoP avatar
MarcoP
User
Posts: 270
Joined: 29-Sep-2004
# Posted on: 19-Jan-2011 15:40:22   

Otis wrote:

The only way I see how you can do this is by creating your own Expression visitor. But this will be complicated as a predicate expression can be deep and complex.

if he wants to see the queries executed, he can switch on tracing, see the queries, copy them to a text editor, switch off tracing and he has the queries. IMHO a much faster way to get this information.

Basically, ever grid in my application is downloaded to excel and the client wanted to add the search criteria to the file. Now its as simple as:

        public static void ExportToCSV<T>(this HttpResponseBase response, Expression expression, IEnumerable<T> data)
        {
            var exportSettings = ExportSettingsRegistry.GetSettings<T>().Snapshot();

            var printable = expression.ToPrintable(exportSettings);

            if (!String.IsNullOrEmpty(printable))
            {
                exportSettings.HeaderLines.Add(Localization.GetString("CDC.Export.Search.FormatString").FormatWith(printable));
            }

            ExportToCSV(response, exportSettings, data);
        }

        public static void ExportToCSV<T>(this HttpResponseBase response, ExportSettings exportSettings, IEnumerable<T> data)
        {
            var converter = new PropertyInfoToCSVConverter<T>();

            response.Clear();
            response.AddHeader("Cache-Control", "must-revalidate");
            response.AddHeader("content-disposition", "attachment; filename={0}.csv".FormatWith(exportSettings.FileName));
            response.ContentType = "text/csv";
            response.AddHeader("Pragma", "must-revalidate");
            response.Write(converter.ToCSV(exportSettings, data));
            response.End();
        }

What a coincedence, I just got done implementing this using the ExpressionVisitor class! Luckily my search screens are not very complex and this works perfectly for me.

Thanks!

public class PrintableExpressionVisitor : ExpressionVisitor
    {
        private StringBuilder sb;
        private ExportSettings exportSettings;

        public string Parse(Expression expression, ExportSettings exportSettings)
        {
            this.sb = new StringBuilder();
            this.exportSettings = exportSettings;           

            Visit(Evaluator.PartialEval(expression));

            return sb.ToString();
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            if (node.Value == null)
            {
                sb.AppendFormat("NULL");
            }
            else
            {
                var value = node.Value.ToString();

                if (node.Value is DateTime)
                {
                    value = ((DateTime)node.Value).ToShortDateString();
                }

                sb.AppendFormat("'{0}'", value);
            }

            return node;
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Expression != null && node.Expression.NodeType == ExpressionType.Parameter)
            {
                sb.Append(GetPrintableName(node.Member.Name));
                return node;
            }
            throw new NotSupportedException(string.Format("The member '{0}' is not supported", node.Member.Name));
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Method.Name == "Contains")
            {
                this.Visit(node.Object);

                sb.Append(" CONTAINS ");

                this.Visit(node.Arguments[0]);
            }

            return node;
        }

        protected override Expression VisitBinary(BinaryExpression node)
        {
            this.Visit(node.Left);

            switch (node.NodeType)
            {
                case ExpressionType.And:
                    sb.Append(" AND ");
                    break;
                case ExpressionType.Or:
                    sb.Append(" OR");
                    break;
                case ExpressionType.Equal:
                    sb.Append(" EQUALS ");
                    break;
                case ExpressionType.NotEqual:
                    sb.Append(" NOT EQUAL TO ");
                    break;
                case ExpressionType.LessThan:
                    sb.Append(" LESS THAN ");
                    break;
                case ExpressionType.LessThanOrEqual:
                    sb.Append(" LESS THAN EQUAL TO ");
                    break;
                case ExpressionType.GreaterThan:
                    sb.Append(" GREATER THAN ");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    sb.Append(" GREATER THAN EQUAL TO ");
                    break;
                case ExpressionType.AndAlso:
                    sb.Append(" AND ");
                    break;
                case ExpressionType.OrElse:
                    sb.Append(" OR ");
                    break;
                default:
                    throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", node.NodeType));
            }

            this.Visit(node.Right);

            return node;
        }

        private string GetPrintableName(string name)
        {
            if (exportSettings == null)
                return name;

            var column = exportSettings.Columns.FirstOrDefault(x => String.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase));

            return column != null ? column.Title : name;
        }
    }

    /// <summary>
    /// Borrowed this class from Microsoft's Matt Warren's Blog.
    /// </summary>
    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);
            }

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

            public 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;
            }
        }
    }
Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 19-Jan-2011 15:51:21   

Thanks for the contribution.