Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

Modifying LINQ Expressions with Rewrite Rules

, 18 Mar 2008
Rewriting query expressions is a simple and yet safe and powerful technique to modify queries dynamically at runtime.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Reflection;

using Rewrite;

namespace RewriteSql {
    public interface IOrderByItem<TEntity> {
        Expression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>> OrderByExpr {
            get;
        }
        Expression<Func<IOrderedQueryable<TEntity>, IOrderedQueryable<TEntity>>> ThenByExpr {
            get;
        }
    }

    public class OrderByItem<TEntity, TKey>: IOrderByItem<TEntity> {
        private Expression<Func<TEntity, TKey>> _keySelector;
        private bool _ascending;

        public OrderByItem( Expression<Func<TEntity, TKey>> keySelector, bool ascending ) {
            _keySelector = keySelector;
            _ascending = ascending;
        }

        /// <summary>
        /// Constructs generic OrderByExpressionBilder from PropertyInfo. 
        /// </summary>
        /// <param name="propertyName"></param>
        public OrderByItem( PropertyInfo pi, bool ascending ) {
            _ascending = ascending;

            ParameterExpression p = Expression.Parameter( typeof( TEntity ), "x" );
            _keySelector = (Expression<Func<TEntity, TKey>>)Expression.Lambda(
                Expression.MakeMemberAccess( p, pi ),
                p );
        }

        public Expression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>> OrderByExpr {
            get {
                Func<TEntity, TKey> dummy = null;
                Expression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>> ret;
                if( _ascending )
                    ret = x => x.OrderBy( y => dummy( y));
                else
                    ret = x => x.OrderByDescending( y => dummy( y));
                return SimpleRewriter.ApplyOnce( ret, Rule.Create( y => dummy( y ), _keySelector ) );
            }
        }

        public Expression<Func<IOrderedQueryable<TEntity>, IOrderedQueryable<TEntity>>> ThenByExpr {
            get {
                Func<TEntity, TKey> dummy = null;
                Expression<Func<IOrderedQueryable<TEntity>, IOrderedQueryable<TEntity>>> ret;
                if( _ascending )
                    ret = x => x.ThenBy( y => dummy( y ) );
                else
                    ret = x => x.ThenByDescending( y => dummy( y ) );
                return SimpleRewriter.ApplyOnce( ret, Rule.Create( y => dummy( y ), _keySelector ) );
            }
        }

    }

    public static class OrderByBuilder<TEntity>{
        public static Expression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>> MakeOrderByClause(
        params IOrderByItem<TEntity>[] args ) {
            bool firstItem = true;
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> oby = null;
            Func<IOrderedQueryable<TEntity>, IOrderedQueryable<TEntity>> thby1 = null;
            Func<IOrderedQueryable<TEntity>, IOrderedQueryable<TEntity>> thby2 = null;
            Expression<Func<IOrderedQueryable<TEntity>, IOrderedQueryable<TEntity>>> lhs1 = x => thby1( x );
            Expression<Func<IOrderedQueryable<TEntity>, IOrderedQueryable<TEntity>>> rhs1 = x => thby1( thby2( x ) );

            Expression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>> ret = x => thby1( oby( x ) );
            SimpleRewriter rwr = new SimpleRewriter( ret );
            foreach( var arg in args.Where( a => a != null ) ) {
                if( firstItem ) {
                    firstItem = false;
                    // replace oby --> arg.OrderByExpr
                    rwr.ApplyOnce( Rule.Create( x => oby( x ), arg.OrderByExpr ) );
                } else {
                    // replace thby1 --> thby2
                    rwr.ApplyOnce( new Rule( lhs1, rhs1 ) );
                    // replace thby2 --> arg.ThenByExpr
                    rwr.ApplyOnce( Rule.Create( x => thby2( x ), arg.ThenByExpr ) );
                }
            }

            if( firstItem )
                return null; // -------------->>>>>>>>>>>>>>>>>>>>>>

            // remove thby1: replace thby1( x) --> x
            rwr.ApplyOnce( Rule.Create( lhs1, x => x ) );
            return (Expression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>>)rwr.Expression;
        }
        
        /// <summary>
        /// Constructs generic OrderByExpressionBilder from a property name. 
        /// </summary>
        /// <param name="propertyName"></param>
        public static IOrderByItem<TEntity> BuildGeneric( string propName, bool ascending ) {
            PropertyInfo pi = typeof( TEntity ).GetProperty( propName );
            if( pi == null )
                throw new ArgumentOutOfRangeException(
                    "propName",
                    string.Format( "Property '{0}' not found in type '{1}'.",
                        propName, typeof( TEntity ).Name ) );
            
            Type[] genTypes = { typeof( TEntity ), pi.PropertyType };
            Type type = typeof( OrderByItem<,> ).MakeGenericType( genTypes );
            return (IOrderByItem<TEntity>)Activator.CreateInstance( type, pi, ascending );
        }
    }

    public static class OrderByRewriter {
        public static Expression Rewrite<TEntity>( Expression expr,
            Expression<Func<TEntity, object>> srcLhs,
            Expression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>> orderByClause ) 
        {
            if( expr == null )
                throw new ArgumentNullException( "expr", "Expression is null" );
            if( srcLhs == null )
                throw new ArgumentNullException( "srcLhs", "Lhs to find dummy order-by selector is null" );

            var rwr = new SimpleRewriter( expr );

            // 1. Replace the orderby key selector defined by srcLhs for localy defined delegate ks.
            Func<TEntity, object> ks = null;
            Expression<Func<TEntity, object>> srcRhs = e => ks( e );
            rwr.ApplyOnce( new Rule( srcLhs, srcRhs ) );
            // Now we have somewhere in the query expression a sub-expression: x.OrderBy( y => ks( y))

            if( orderByClause == null ) {
                // no orderby items at all
                rwr.ApplyOnce( MkQueryableRule<TEntity>( x => x.OrderBy( y => ks( y ) ), x => x ) );
            } else {
                rwr.ApplyOnce( MkOrderByRule( x => x.OrderBy( y => ks( y ) ), orderByClause ) );
            }

            return rwr.Expression;
        }

        private static Rule MkQueryableRule<TEntity>(
            Expression<Func<IQueryable<TEntity>, IQueryable<TEntity>>> lhs,
            Expression<Func<IQueryable<TEntity>, IQueryable<TEntity>>> rhs ) {
            return new Rule( lhs, rhs );
        }
        
        private static Rule MkOrderByRule<TEntity>(
            Expression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>> lhs,
            Expression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>> rhs ) {
            return new Rule( lhs, rhs );
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Dmitri Raiko

Germany Germany
No Biography provided

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 18 Mar 2008
Article Copyright 2008 by Dmitri Raiko
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid