How can I write this subquery using expression trees?

Motivation

I have a LINQ-to-SQL query that I need to write for production code using generics and dynamic types. With help from other SO users, I was able to determine that I needed to create a query using expression trees. To get familiar with expression trees, I am first trying to write an older typed version, but I am having a hard time understanding some of the nested queries.

I am using MSDN as a reference primarily for this article .

A type

This is the type that will be used in the sample request. This is a simplified version of the class used to generate a dynamic column count grid from a SQL database.

public class PivotElement {
    public int Key { get; set; }
    public string DataField { get; set; }
    public string ColumnText { get; set; }
}

      

Final query

This is the query I would like to build, written in the extension syntax:

IQueryable<PivotElement> iQ = ...;
var copy = iQ;

// filterColumn and filterValue are strings that were passed in

copy = copy.Where( 
    pe1 => theIQ.Where( 
        pe2 => 
            pe1.Key == pe1.Key && 
            pe2.DataField == filterColumn && 
            pgr2.ColumnText.Contains( filterValue )
    ).Any()
);

      

This query filters out rows that do not contain the requested text in the requested column before items are rotated.

Still

This is what I have so far. I think this is mostly correct, but I am not sure how to specify that the inner one Where

should always be called on theIQ

.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace IQueryableWhereTypeChange {
    class Program {
        static void Main( string[] args ) {
            var list = new List<PivotElement>() {
                new PivotElement() {
                    Key = 1, DataField = "FirstName", ColumnText = "Bob"
                },
                new PivotElement() {
                    Key = 1, DataField = "LastName", ColumnText = "Sanders"
                },
                new PivotElement() {
                    Key = 2, DataField = "FirstName", ColumnText = "Bob"
                },
                new PivotElement() {
                    Key = 2, DataField = "LastName", ColumnText = "Smith"
                },
                new PivotElement() {
                    Key = 3, DataField = "FirstName", ColumnText = "John"
                },
                new PivotElement() {
                    Key = 3, DataField = "LastName", ColumnText = "Smith"
                }
            };


            var theIQ = list.AsQueryable();
            var iQCopy = theIQ;

            var elementType = typeof( PivotElement );
            var delegateType = typeof( Func<PivotElement, bool> );

            var filterColumn = "LastName";
            var filterValue = "Smith";

            // Query is
            // iQCopy = iQCopy.Where( 
            //    pe1 => iQ.Where( 
            //        pe2 => 
            //            pe1.Key == pe1.Key && 
            //            pe2.DataField == filterColumn && 
            //            pgr2.ColumnText.Contains( filterValue )
            //    ).Any()
            //);

            // So all the elements for keys 2 and 3 should be in the
            //  result set, as those keys have a last name of Smith

            // build pe1
            Type elementType = typeof( PivotElement );
            ParameterExpression pe1 = Expression.Parameter( elementType, "pe1" );
            // build pe2
            ParameterExpression pe2 = Expression.Parameter( elementType, "pe2" );

            // pe1.Key
            Expression pe1KeyProp = Expression.Property( 
                pe1, elementType.GetProperty( "Key" )
            );
            // pe2.Key
            Expression pe2KeyProp = Expression.Property( 
                pe2, elementType.GetProperty( "Key" )
            );
            // build pe1.Key == pe2.Key
            Expression keyEquals = Expression.Equal( pe1KeyProp, pe2KeyProp );

            // build pe2.Datafield
            Expression pe2Datafield = Expression.Property( 
                pe2, elementType.GetProperty( "DataField" )
            );
            // build pe2.DataField == filterColumn
            Expression columnExpression = Expression.Constant( filterColumn );
            Expression dataEquals = Expression.Equal( 
                pe2Datafield, columnExpression 
            );

            // build pe2.ColumnText
            Expression pe2ColumnText = Expression.Property( 
                pe2, elementType.GetProperty( "ColumnText" )
            );
            // build pe2.ColumnText.Contains( filterValue )
            Type stringType = typeof(string);
            Expression valueExpression = Expression.Constant( filterValue );
            Expression textContains = Expression.Call( 
                pe2ColumnText, 
                stringType.GetMethod(
                    "Contains", 
                    new Type[] { stringType } ), 
                    new Expression[] { valueExpression }
            );

            // build pe1.Key == pe2.Key &&
            //       pe2.DataField == filterColumn && 
            //       pe2.ColumnText.Contains( filterValue )
            Expression innerCondition = Expression.AndAlso(
                keyEquals, Expression.AndAlso( dataEquals, textContains )
            );

            // build theIQ.Where( pe2 => innerCondition )
            // build theIQ.Where( pe2 => innerCondition ).Any()
            // build iQCopy.Where( pe1 => anyCall )
            // create the final query

            // enumerate results
            foreach( var pe in results ) {
                Console.WriteLine( 
                    "Key: " + pe.Key + 
                    ", DataField: " + pe.DataField + 
                    ", ColumnText: " + pe.ColumnText 
                );
            }
        }
    }

    public class PivotElement {
        public int Key { get; set; }
        public string DataField { get; set; }
        public string ColumnText { get; set; }
    }
}

      

+3


source to share


1 answer


The missing bit amazed me when I got this whole question. Since I already typed it all in, I went ahead and posted it. Here are the missing bits, in case anyone is wondering:

// build theIQ.Where( pe2 => innerCondition )
Type queryableType = typeof( Queryable );
var delegateType = typeof( Func<PivotElement, bool> );
MethodCallExpression innerWhere = Expression.Call( 
    queryableType, 
    "Where", 
    new Type[] { elementType }, 
    new Expression[] { 
        theIQ.Expression, 
        Expression.Lambda(
            delegateType, innerCondition, new ParameterExpression[] { pe2 } 
        )
    } 
);

// build theIQ.Where( pe2 => innerWhere ).Any()
MethodCallExpression anyCall = Expression.Call( 
    queryableType, "Any", new Type[] { elementType }, innerWhere 
);

// build iQCopy.Where( pe1 => anyCall )
MethodCallExpression outerWhere = Expression.Call( 
    queryableType, 
    "Where",
    new Type[] { elementType }, 
    new Expression[] { 
        iQCopy.Expression, 
        Expression.Lambda( 
            delegateType, anyCall, new ParameterExpression[] { pe1 } 
        )
    }
);

// create the final query
var results = iQCopy.Provider.CreateQuery<PivotElement>( outerWhere );

      



theIQ.Expression

- that's what it says to use the structure of the query theIQ

, iQCopy.Expression

- that's what it says to use the structure of the request of iQCopy

, and the final iQCopy.Provider

- that's what it says to use query the actual instance iQCopy

.

+2


source







All Articles