如何使用表达式树编写此嵌套查询

本文关键字:嵌套 查询 何使用 表达式 | 更新日期: 2023-09-27 18:21:50

Motivation

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

我一直在使用 MSDN 作为参考,主要是这篇文章。

类型

这是将在示例查询中使用的类型。它是类的简化版本,用于创建具有 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; }
    }
}

如何使用表达式树编写此嵌套查询

缺少的一点击中了我,因为我正在把这个问题全部格式化。由于我已经全部输入了,所以我继续发布它。如果有人感兴趣,以下是缺失的部分:

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