Click here to Skip to main content
12,303,670 members (75,045 online)
Click here to Skip to main content

Tagged as

Stats

26.1K views
214 downloads
30 bookmarked
Posted

Black Art – LINQ expressions reuse

, 9 Aug 2012 CDDL
When developing a complex line of business system queries, reuse is often required. This article provides some guidelines and tools for LINQ expressions reuse.
LinqExpressionsReuse.suo
LinqExpressionProjection
bin
Debug
LinqExpressionProjection.dll
LinqExpressionProjection.pdb
Release
LinqExpressionProjection.csproj.vspscc
obj
Debug
DesignTimeResolveAssemblyReferencesInput.cache
LinqExpressionProjection.dll
LinqExpressionProjection.pdb
TempPE
Properties
LinqExpressionProjection.Test
bin
Debug
Dependencies
EntityFramework.dll
EntityFramework.dll
LinqExpressionProjection.dll
LinqExpressionProjection.pdb
LinqExpressionProjection.Test.dll
LinqExpressionProjection.Test.pdb
Release
Dependencies
EntityFramework.dll
LinqExpressionProjection.Test.csproj.vspscc
Model
obj
Debug
DesignTimeResolveAssemblyReferencesInput.cache
LinqExpressionProjection.Test.dll
LinqExpressionProjection.Test.pdb
ResolveAssemblyReference.cache
TempPE
Properties
LinqExpressionsReuse
bin
Debug
EF_ReuseLinq.exe
EF_ReuseLinq.pdb
EF_ReuseLinq.vshost.exe
EF_ReuseLinq.vshost.exe.manifest
EntityFramework.dll
LinqExpressionProjection.dll
LinqExpressionProjection.pdb
Dependencies
EntityFramework.dll
EF_ReuseLinq.csproj.user
Model
obj
x86
Debug
DesignTimeResolveAssemblyReferencesInput.cache
EF_ReuseLinq.exe
EF_ReuseLinq.pdb
ResolveAssemblyReference.cache
TempPE
Properties
TestResults
Asher_PC1600 2012-06-12 16_37_14.trx
Asher_PC1600 2012-06-12 16_37_14
Out
EntityFramework.dll
LinqExpressionProjection.dll
LinqExpressionProjection.pdb
linqexpressionprojection.test.dll
LinqExpressionProjection.Test.pdb
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Collections.ObjectModel;
using System.Reflection;

namespace LinqExpressionProjection
{
    /// <summary>
    /// Custom expresssion visitor for ExpandableQuery. This expands calls to Expression.Compile() and
    /// collapses captured lambda references in subqueries which LINQ to SQL can't otherwise handle.
    /// </summary>
    class ProjectionExpressionExpander : ExpressionVisitor
    {
        internal ProjectionExpressionExpander() { }

        Stack<MethodCallExpression> _selectStack = new Stack<MethodCallExpression>();

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            Expression visitedMethodCall = null;

            if (m.Method.Name == "Select")
            {
                _selectStack.Push(m);
            }

            if (m.Method.Name == "Project" && m.Method.DeclaringType == typeof(Extensions))
            {
                // Project() is expected to be invoked on an Expression<Func<In, Out>>,
                // the expression can be contained in a local variable, a field, retrived from a method
                // or any other expression tree as long as it can be compiled and executed to return a proper lambda
                ParameterExpression selectParameter = GetSelectParameter(_selectStack.Peek());
                LambdaExpression innerLambda = GetInnerLambda(m.Arguments[0], selectParameter.Type, m.Method.ReturnType);

                // By returning the visited body of the lambda we ommit the call to Project()
                // and the tree producing the selector lambda:
                visitedMethodCall = Visit(innerLambda.Body);

                // The lambda takes a parameter, this parameter must be replaced by the parameter provided for the seelct lambda
                // Rebind parameter:
                var map = new Dictionary<ParameterExpression, ParameterExpression>()
                                  {
                                      {
                                          innerLambda.Parameters[0],
                                          selectParameter
                                          }
                                  };
                visitedMethodCall = new ParameterRebinder(map).Visit(visitedMethodCall);
            }

            if (visitedMethodCall == null)
            {
                visitedMethodCall = base.VisitMethodCall(m);
            }

            if (m.Method.Name == "Select")
            {
                _selectStack.Pop();
            }
            return visitedMethodCall;
        }

        /// <summary>
        /// This method executes expression and expects it to return a lambda expression taking paramter of 
        /// certain type and returning a value of certain type.
        /// </summary>
        private LambdaExpression GetInnerLambda(Expression projectionExpression, Type parameterType, Type returnType)
        {
            Exception innerException = null;
            try
            {
                Expression<Func<LambdaExpression>> executionLambda = Expression.Lambda<Func<LambdaExpression>>(projectionExpression);
                LambdaExpression extractedLambda = executionLambda.Compile().Invoke();
                if (extractedLambda != null
                    && extractedLambda.Parameters[0].Type == parameterType
                    && extractedLambda.ReturnType == returnType)
                {
                    return extractedLambda;
                }
            }
            catch (Exception e)
            {
                innerException = e;
            }
            throw new InvalidOperationException(string.Format("Lambda expression with parameter of type '{0}' and return type '{1}' was not located after Project() call ({2})", parameterType, returnType, projectionExpression), innerException);
        }

        private ParameterExpression GetSelectParameter(MethodCallExpression selectExpression)
        {
            LambdaExpression selectionLambda = SkipUnwantedExpressions(selectExpression.Arguments[1]) as LambdaExpression;
            if (selectionLambda != null)
            {
                return selectionLambda.Parameters[0];
            }
            throw new InvalidOperationException(string.Format("Lambda not found in select expression '{0}'", selectExpression));
        }

        private Expression SkipUnwantedExpressions(Expression expression)
        {
            if (expression is UnaryExpression)
            {
                return SkipUnwantedExpressions(((UnaryExpression)expression).Operand);
            }
            return expression;
        }
    }
}

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 Common Development and Distribution License (CDDL)

Share

About the Author

Asher Barak
Chief Technology Officer Ziv systems, Israel
Israel Israel
Starting with Apple IIe BASICA, and working my way through Pascal, Power Builder, Visual basic (and the light office VBA) C, C++, I am now a full stack developer and development manager. Mostly with MS technologies on the server side and javascript(typescript) frameworks on the client side.

You may also be interested in...

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.160530.1 | Last Updated 9 Aug 2012
Article Copyright 2012 by Asher Barak
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid