Click here to Skip to main content
15,891,777 members
Articles / Programming Languages / C#

Black Art – LINQ expressions reuse

Rate me:
Please Sign up or sign in to vote.
4.94/5 (16 votes)
2 Apr 2017CDDL8 min read 61.7K   312   39  
When developing a complex line of business system queries, reuse is often required. This article provides some guidelines and tools for LINQ expressions reuse.
using System;
using System.Linq.Expressions;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using LinqExpressionProjection.Test.Model;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace LinqExpressionProjection.Test
{
    public delegate bool MyDel(Project p);

    [TestClass]
    public class LinqExpressionsProjection_TestFixture
    {
        [TestMethod]
        public void Trials()
        {
            Func<Project, bool> p1 = p => p.Subprojects.Count() < 2;
            Expression<Func<Project, bool>> p2 = p => p.Subprojects.Count() < 2;
            //var p3 = (Project p) => p.Subprojects.Count() < 2;
            MyDel d = MyPredicate;
            Predicate<Project> pp = MyPredicate;
            using (var ctx = new ProjectsDbContext())
            {
                var v = ctx.
                    Projects.AsExpressionProjectable()
                    .Select(p => new
                                     {
                                         Proj = p,
                                         AA = _AASelector.Project<double>(),
                                         AA2 = p.Subprojects.Where(sp => sp.Area < 1000)
                                     .Average(sp => sp.Area)

                                     }).Where(o => o.AA < 300);
                var v2=v.ToArray();

            }
        }

        private Expression<Func<Project, double>> _AASelector =
            project =>
            project.Subprojects.Where(sp => sp.Area < 1000)
                .Average(sp => sp.Area);

        public bool MyPredicate(Project p)
        {
            return p.Subprojects.Count() > 2;
        }


        private static Expression<Func<Project, double>> _projectAverageEffectiveAreaSelectorStatic =
                proj => proj.Subprojects.Where(sp => sp.Area < 1000).Average(sp => sp.Area);

        private Expression<Func<Project, double>> _projectAverageEffectiveAreaSelector =
        proj => proj.Subprojects.Where(sp => sp.Area < 1000).Average(sp => sp.Area);

        public static Expression<Func<Project, double>> GetProjectAverageEffectiveAreaSelectorStatic()
        {
            return proj => proj.Subprojects.Where(sp => sp.Area < 1000).Average(sp => sp.Area);
        }

        public Expression<Func<Project, double>> GetProjectAverageEffectiveAreaSelector()
        {
            return proj => proj.Subprojects.Where(sp => sp.Area < 1000).Average(sp => sp.Area);
        }

        public Expression<Func<Project, double>> GetProjectAverageEffectiveAreaSelectorWithLogic(bool isOverThousandIncluded = false)
        {
            return isOverThousandIncluded
            ? (Expression<Func<Project, double>>)((Project proj) => proj.Subprojects.Where(sp => sp.Area < 1000).Average(sp => sp.Area))
            : (Project proj) => proj.Subprojects.Average(sp => sp.Area);
        }

        [TestMethod]
        [ExpectedException(typeof(NotSupportedException))]
        public void ProjectingExpressionFailsOnNormalCases_Test()
        {
            ValidateDb();
            Expression<Func<Project, double>> localSelector =
                proj => proj.Subprojects.Where(sp => sp.Area < 1000).Average(sp => sp.Area);
            using (var ctx = new ProjectsDbContext())
            {
                var v = (from p in ctx.Projects
                         select new
                         {
                             Project = p,
                             AEA = localSelector
                         }).ToArray();
            }
        }

        [TestMethod]
        [ExpectedException(typeof(NotSupportedException))]
        public void ProjectingExpressionFailsWithNoCallToAsExpressionProjectable_Test()
        {
            ValidateDb();
            Expression<Func<Project, double>> localSelector =
                proj => proj.Subprojects.Where(sp => sp.Area < 1000).Average(sp => sp.Area);
            using (var ctx = new ProjectsDbContext())
            {
                var projects = (from p in ctx.Projects
                                select new
                                {
                                    Project = p,
                                    AEA = localSelector.Project<double>()
                                }).ToArray();
                Assert.AreEqual(150, projects[0].AEA);
                Assert.AreEqual(400, projects[1].AEA);
            }
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ProjectingExpressionFailsWithProjectionNotMatchingLambdaReturnType_Test()
        {
            ValidateDb();
            Expression<Func<Project, double>> localSelector =
                proj => proj.Subprojects.Where(sp => sp.Area < 1000).Average(sp => sp.Area);
            using (var ctx = new ProjectsDbContext())
            {
                var projects = (from p in ctx.Projects.AsExpressionProjectable()
                                select new
                                {
                                    Project = p,
                                    AEA = localSelector.Project<int>()
                                }).ToArray();
                Assert.AreEqual(150, projects[0].AEA);
                Assert.AreEqual(400, projects[1].AEA);
            }
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ProjectingExpressionFailsWithWrongLambdaParameterType_Test()
        {
            ValidateDb();
            Expression<Func<Subproject, double>> localSelector =
                sp => sp.Area;
            using (var ctx = new ProjectsDbContext())
            {
                var projects = (from p in ctx.Projects.AsExpressionProjectable()
                                select new
                                {
                                    Project = p,
                                    AEA = localSelector.Project<double>()
                                }).ToArray();
                Assert.AreEqual(150, projects[0].AEA);
                Assert.AreEqual(400, projects[1].AEA);
            }
        }

        [TestMethod]
        public void ProjectingExpressionByLocalVariable_Test()
        {
            ValidateDb();
            Expression<Func<Project, double>> localSelector =
                proj => proj.Subprojects.Where(sp => sp.Area < 1000).Average(sp => sp.Area);
            using (var ctx = new ProjectsDbContext())
            {
                var projects = (from p in ctx.Projects.AsExpressionProjectable()
                                select new
                                           {
                                               Project = p,
                                               AEA = localSelector.Project<double>()
                                           }).ToArray();
                Assert.AreEqual(150, projects[0].AEA);
                Assert.AreEqual(400, projects[1].AEA);
            }
        }

        [TestMethod]
        public void ProjectingExpressionByStaticField_Test()
        {
            ValidateDb();
            using (var ctx = new ProjectsDbContext())
            {
                var projects = (from p in ctx.Projects.AsExpressionProjectable()
                                select new
                                           {
                                               Project = p,
                                               AEA = _projectAverageEffectiveAreaSelectorStatic.Project<double>()
                                           }).ToArray();
                Assert.AreEqual(150, projects[0].AEA);
                Assert.AreEqual(400, projects[1].AEA);
            }
        }


        [TestMethod]
        public void ProjectingExpressionByNonStaticField_Test()
        {
            ValidateDb();
            using (var ctx = new ProjectsDbContext())
            {
                var projects = (from p in ctx.Projects.AsExpressionProjectable()
                                select new
                                {
                                    Project = p,
                                    AEA = _projectAverageEffectiveAreaSelector.Project<double>()
                                }).ToArray();
                Assert.AreEqual(150, projects[0].AEA);
                Assert.AreEqual(400, projects[1].AEA);
            }
        }

        [TestMethod]
        public void ProjectingExpressionByStaticMethod_Test()
        {
            ValidateDb();
            using (var ctx = new ProjectsDbContext())
            {
                var projects = (from p in ctx.Projects.AsExpressionProjectable()
                                select new
                                {
                                    Project = p,
                                    AEA = GetProjectAverageEffectiveAreaSelectorStatic().Project<double>()
                                }).ToArray();
                Assert.AreEqual(150, projects[0].AEA);
                Assert.AreEqual(400, projects[1].AEA);
            }
        }

        [TestMethod]
        public void ProjectingExpressionByNonStaticMethod_Test()
        {
            ValidateDb();
            using (var ctx = new ProjectsDbContext())
            {
                var projects = (from p in ctx.Projects.AsExpressionProjectable()
                                select new
                                {
                                    Project = p,
                                    AEA = GetProjectAverageEffectiveAreaSelector().Project<double>()
                                }).ToArray();
                Assert.AreEqual(150, projects[0].AEA);
                Assert.AreEqual(400, projects[1].AEA);
            }
        }

        [TestMethod]
        public void ProjectingExpressionByNonStaticMethodWithLogic_Test()
        {
            ValidateDb();
            using (var ctx = new ProjectsDbContext())
            {
                var projects = (from p in ctx.Projects.AsExpressionProjectable()
                                select new
                                {
                                    Project = p,
                                    AEA = GetProjectAverageEffectiveAreaSelectorWithLogic(false).Project<double>()
                                }).ToArray();
                Assert.AreEqual(150, projects[0].AEA);
                Assert.AreEqual(3600, projects[1].AEA);
            }
            using (var ctx = new ProjectsDbContext())
            {
                var projects = (from p in ctx.Projects.AsExpressionProjectable()
                                select new
                                {
                                    Project = p,
                                    AEA = GetProjectAverageEffectiveAreaSelectorWithLogic(true).Project<double>()
                                }).ToArray();
                Assert.AreEqual(150, projects[0].AEA);
                Assert.AreEqual(400, projects[1].AEA);
            }
        }

        private static void ValidateDb()
        {
            using (var ctx = new ProjectsDbContext())
            {
                try
                {
                    if (ctx.Projects.Count() != 2 || ctx.Subprojects.Count() != 5)
                    {
                        ClearDb();
                        PopulateDb();
                    }
                }
                catch (Exception)
                {
                    try
                    {
                        ClearDb();
                    }
                    catch (Exception)
                    {
                    }
                    PopulateDb();
                }
            }
        }

        private static void ClearDb()
        {
            using (var ctx = new ProjectsDbContext())
            {
                foreach (var subproject in ctx.Subprojects)
                {
                    ctx.Subprojects.Remove(subproject);
                }
                foreach (var project in ctx.Projects)
                {
                    ctx.Projects.Remove(project);
                }
                ctx.SaveChanges();
            }
        }

        private static void PopulateDb()
        {
            using (var ctx = new ProjectsDbContext())
            {
                Project p1 = ctx.Projects.Add(new Project());
                Project p2 = ctx.Projects.Add(new Project());

                ctx.Subprojects.Add(new Subproject() { Area = 100, Project = p1 });
                ctx.Subprojects.Add(new Subproject() { Area = 200, Project = p1 });
                ctx.Subprojects.Add(new Subproject() { Area = 350, Project = p2 });
                ctx.Subprojects.Add(new Subproject() { Area = 450, Project = p2 });
                ctx.Subprojects.Add(new Subproject() { Area = 10000, Project = p2 });

                ctx.SaveChanges();
            }
        }
    }
}

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)


Written By
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.

Comments and Discussions