如何用表达式树写这个嵌套查询?

时间:2014-09-13 21:56:35

标签: c# linq expression-trees nested-queries

动机

我有一个LINQ-to-SQL查询,我需要为涉及泛型和动态类型的生产代码编写。在SO的其他人的帮助下,我已经能够确定我需要使用表达式树创建查询。为了熟悉表达式树,我首先尝试编写一个更简单的静态类型版本,但是我在查找嵌套查询部分时遇到了麻烦。

我一直在使用MSDN作为参考,主要是this article

类型

这是将在示例查询中使用的类型。它是一个类的简化版本,用于从SQL数据库创建具有动态列数的网格。

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

结果查询

这是我想最终构建的查询,用扩展语法编写:

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()
);

此参数会筛选出在元素轮换之前不会在请求的列中包含所请求文本的行。

到目前为止

这是我到目前为止所得到的。我认为这大多是正确的,但我不确定如何表明应始终在Where上调用内部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; }
    }
}

1 个答案:

答案 0 :(得分:2)

当我把这个问题全部格式化时,丢失的一点击中了我。因为我已经把它全部输了,所以我继续发布它。如果有人有兴趣,这里有缺失的部分:

// 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告诉它使用theIQ的查询结构,iQCopy.Expression告诉它使用iQCopy的查询结构,最后{{1}是什么告诉它将构造的查询应用于实际的iQCopy.Provider实例。