zoukankan      html  css  js  c++  java
  • C# 动态构建表达式树(二)——构建 Select 和 GroupBy 的表达式

    C# 动态构建表达式树(二)——构建 Select 和 GroupBy 的表达式

    前言

    上篇中写了表达式的基本使用,为 Where 方法动态构建了表达式。在这篇中会写如何为 Select 和 GroupBy 动态构建(可以理解为动态表达式的其它常见形式)。

    本文的操作方式似乎在实际使用中作用甚微,仅作为了解即可

    准备工作

    环境:.NET Framework 4.5,SQLServer 2017

    建表脚本如下(由 SSMS 导出):

    USE [default]
    GO
    /****** Object:  Table [dbo].[Person]    Script Date: 2021/6/9 12:06:43 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    CREATE TABLE [dbo].[Person](
    	[Id] [varchar](100) NOT NULL,
    	[Name] [nvarchar](50) NOT NULL,
    	[Age] [int] NOT NULL,
    	[Gender] [nvarchar](5) NOT NULL,
    	[Point] [int] NOT NULL,
    	[CreateTime] [datetime] NOT NULL,
     CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED 
    (
    	[Id] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO
    

    为 Select 方法动态构建表达式

    假设我们要查出 Person 表中的 Name、Age、Gender 字段,返回类型为 List<PersonResult> 的对象:

    class PersonResult
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Gender { get; set; }
    }
    

    常规写法:

    List<PersonResult> personList 
    	= context.Person.Select(p => new PersonResult
    								{
    								    Name = p.Name,
    								    Age = p.Age,
    								    Gender = p.Gender
    								}).ToList();
    

    动态组合写法:

    ParameterExpression pe = Expression.Parameter(typeof(Person), "p");	# 创建形参 p
    
    MemberExpression meName = Expression.MakeMemberAccess(pe, typeof(Person).GetProperty("Name"));	# 要使用 MakeMemberAccess 方法
    MemberExpression meAge = Expression.MakeMemberAccess(pe, typeof(Person).GetProperty("Age"));
    MemberExpression meGender = Expression.MakeMemberAccess(pe, typeof(Person).GetProperty("Gender"));
    
    Type personResultType = typeof(PersonResult);
    MemberAssignment maName = Expression.Bind(personResultType.GetProperty("Name"), meName);	# 使用 Bind 方法将目标类型的属性与源类型的属性值绑定
    MemberAssignment maAge = Expression.Bind(personResultType.GetProperty("Age"), meAge);
    MemberAssignment maGender = Expression.Bind(personResultType.GetProperty("Gender"), meGender);
    
    NewExpression ne = Expression.New(personResultType);	# 相当于 new 关键字创建一个对象
    
    MemberInitExpression mie = Expression.MemberInit(ne, maName, maAge, maGender);	# 相当于初始化时赋值操作
    
    Expression<Func<Person, PersonResult>> personSelectExpression = Expression.Lambda<Func<Person, PersonResult>>(mie, pe);
    var personList1 = context.Person.Select(personSelectExpression).ToList();
    

    与构建 Where 方法的表达式差不多,主要是创建新对象以及赋值的写法需要注意。

    为 GroupBy 方法动态构建表达式

    假设我们要统计出 Person 表中的男生女生数量,返回类型为 List<PersonGroupByResult> 对象

    class PersonGroupByResult
    {
        public string Gender { get; set; }
        public int Count { get; set; }
    }
    

    常规写法:

    List<PersonGroupByResult> personList =
                    context.Person.GroupBy(p => p.Gender)
                                    .Select(p => new PersonGroupByResult
                                    {
                                        Gender = p.Key,
                                        Count = p.Count()
                                    }).ToList();
    

    动态组合写法:

    // 动态创建 GroupBy 中的 Expression
    ParameterExpression pe = Expression.Parameter(typeof(Person), "p");
    MemberExpression meGender = Expression.Property(pe, "Gender");
    Expression<Func<Person, string>> groupByExpression = Expression.Lambda<Func<Person, string>>(meGender, pe); 
    
    // 动态创建 Select 中的 Expression
    Type groupType = typeof(IGrouping<string, Person>);	# 注意 GroupBy 函数返回的类型
    ParameterExpression pge = Expression.Parameter(groupType, "pg");
    MemberExpression meKeyGender = Expression.MakeMemberAccess(pge, groupType.GetProperty("Key"));	# 获取其中的属性,与上面动态拼接 Select 相同           
    
    Type groupByResultType = typeof(PersonGroupByResult);
    MemberAssignment maGender = Expression.Bind(groupByResultType.GetProperty("Gender"), meKeyGender);	# 使用 Bind 方法将目标类型的属性与源类型的属性值绑定,与上面动态拼接 Select 相同
    MethodInfo countMethod = typeof(Enumerable).GetMethods().Where(a => a.Name == "Count" && a.GetParameters().Length == 1)
                                                .FirstOrDefault().MakeGenericMethod(typeof(Person));	# 获取 Count 方法
    MemberAssignment maCount = Expression.Bind(groupByResultType.GetProperty("Count"), Expression.Call(countMethod, pge));	#使用 Bind 方法将目标类型的属性与源类型调用方法的返回值绑定
    NewExpression ne = Expression.New(groupByResultType);
    MemberInitExpression mie = Expression.MemberInit(ne, maGender, maCount);
    
    Expression<Func<IGrouping<string, Person>, PersonGroupByResult>> personSelectExpression =
    	Expression.Lambda<Func<IGrouping<string, Person>, PersonGroupByResult>>(mie, pge);
    var personList1 = context.Person.GroupBy(groupByExpression).Select(personSelectExpression).ToList();
    

    需要注意的是查找 Count 方法的过程。通过查看定义发现,IGrouping 类型中并没有 Count 方法,而 IGrouping 实现了 IEnumerable,因此想到获取 Enumerable 这个 IEnumerable 实现类中的 Count 方法

    而 Enumerable中 的 Count 方法定义如下:

    在查阅资料和多次尝试后,仍然无法直接获取到 Count(当仅传入方法名称时,提示有多个定义;当传入方法名称和参数时,一直返回为 null)。现通过参数个数来筛选,得到想要的方法。还需要注意的是,Count 方法为泛型方法,得到后还需要执行 MakeGenericMethod 以传入泛型类型

    通过查看 ChangeTracer 和 SQL 执行情况,发现即使我们使用的是 Enumerable 类型,依然是只返回了我们想要的结果,没有全表查询。这其中的奥秘还需要探索啊。

    其它的一点思考

    之前在工作中为了方便经常使用 Select 查询出匿名类,能否使用动态创建表达式的方式创建匿名类呢(我能想到的一种使用场景是,根据某些条件返回不同的字段,但这其实可以通过冗余字段实现)。在进行了很多尝试后,发现只能先写好一个匿名类,再 Select 这个匿名类的相关字段。虽然看似达到了目的,但不符合我们动态组合的要求,因此是没有意义的。

    参考

    c# – 使用反射创建lambda表达式,如x => new {..}

    referencing desired overloaded generic method

    Expression表达式目录树动态拼接 反射获取泛型方法

    How to create LINQ Expression Tree to select an anonymous type

    How to use Expression to build an Anonymous Type?

  • 相关阅读:
    任务安排(代价提前付)
    10 01模拟赛订正
    哈希hash
    初学期望
    P1251 递推专练3
    P1229-神秘岛
    P1228-重叠的图像
    白银莲花池
    求强连通分量
    割边
  • 原文地址:https://www.cnblogs.com/battor/p/expression_select_groupby.html
Copyright © 2011-2022 走看看