(十一)CSharp-LINQ-查询表达式(2)

本文详细介绍了LINQ查询表达式的关键组成部分,包括from子句用于指定数据源和迭代变量,join子句用于合并不同集合的数据,以及查询主体中的from...let...where片段用于过滤和计算。此外,还讨论了orderby子句的排序功能,select...group子句的分组逻辑,以及匿名类型的使用。文章通过实例展示了这些概念在实际编程中的应用。

一、查询表达式

1、查询表达式的结构

查询表达式由 from 子句和查询主体组成。

  • 子句必须按照一定的顺序出现。
  • from 子句和 select…group 子句这两部分是必需的。
  • 其他子句是可选的。
  • 在 LINQ 查询表达式中,select 子句在表达式最后。
  • 可以有任意多的 from…let…where 子句。

请添加图片描述

2、from 子句

from 子句指定了要作为数据源使用的数据集合。它还引入了迭代变量。

要点:

  • 1)迭代变量逐个表示数据源的每一个元素。

  • 2)from 子句的语法如下:

    • Type 是集合中元素的类型。这是可选的,因为编译器可以从集合中推断类型。
    • Item 是迭代变量的名字。
    • Items 是要查询的集合的名字。集合必须是可枚举的。
from Type Item in Items
//迭代变量声明:Type Item in Items
int arr1 = {10,11,12,13};

var query = from item in arr1
		    where item < 13
		    select item;

foreach(var item in query)
Console.Write($"{ item },");
//迭代变量:item

输出结果:

10,11,12,

图演示了 from 子句的语法

请添加图片描述

LINQ 的 from 子句和 foreach 语句非常相似,但主要的不同点如下:

  • foreach 语句命令式地制定了要从第一个到最后一个按顺序访问集合中的项。而 from 子句则声明式地规定集合中的每个项都要被访问,但并没有假定以什么样的顺序。

  • foreach 语句在遇到代码时就执行其主体,而 from 子句什么也不执行。它创建可以执行查询的后台代码对象。只有在程序的控制流遇到访问查询变量的语句时,才会执行查询。

3、join 子句

有关联结的要点:

  • 使用联结来结合两个或更多集合中的数据。
  • 联结操作接受两个集合,然后创建一个临时的对象集合,其中每一个对象包含两个原始集合对象中的所有字段。

联结的语法如下,它指定了第二个集合要和之前子句中的集合进行联结:

join Identifier in Collection2 on Field1 equals Field2
//关键字:join、in、on、equals
//指定另外的集合和 ID 来引用它:Identifier in Collection2
//用于比较相等性的字段:Field1 equals Field2

图20-4演示了 join 子句的语法:

请添加图片描述

join 子句的示例

var query = from s in students
			join c in studentsInCourses on s.StID equals c.StID

//第一个集合和 ID:from s in students
//第二集合和 ID:c in studentsInCourses
//第一个集合的项:s.StID
//第二集合的项:c.StID

什么是联结

LINQ 中的 join 接受两个集合,然后创建一个新的集合,其中每一个元素包含两个原始集合中的元素成员。

public class Student
{
public int StID;
public string LastName;
}

public class CourseStudent
{
public string CourseName;
public inr StID;
}

如图所示:
Student 对象为 students,CourseStudent 对象为 studentsInCourses。

请添加图片描述

*图20-6演示了联结是如何工作的。

如果我们拿第一个学生的记录并把它的 ID 和每一个 studentsInCourses 对象中的学生 ID 进行比较,可以找到两条匹配的记录,中间列的顶部就是。

请添加图片描述

示例:


    class Program
    {
        public class Student
        {
            public int StID;
            public string LastName;
        }

        public class CourseStudent
        {
            public string CourseName;
            public int StID;
        }

        static Student[] students = new Student[]
        {
            new Student { StID = 1, LastName = "Carson" },
            new Student { StID = 2, LastName = "Klassen" },
            new Student { StID = 3, LastName = "Fleming" },

        };

        static CourseStudent[] studentsInCourses = new CourseStudent[]
        {
            new CourseStudent { CourseName = "Art",     StID = 1 },
            new CourseStudent { CourseName = "Art",     StID = 2 },
            new CourseStudent { CourseName = "History",     StID = 1 },
            new CourseStudent { CourseName = "History",     StID = 3 },
            new CourseStudent { CourseName = "Physics",     StID = 3 },
        };

        static void Main(string[] args)
        {
            var query = from s in students
                        join c in studentsInCourses on s.StID equals c.StID
                        where c.CourseName == "History"
                        select s.LastName;

            foreach (var q in query)
                Console.WriteLine($"Student taking History: { q }");
                             
            Console.ReadKey();
        }
    }

输出结果:

Student taking History: Carson
Student taking History: Fleming

4、查询主体中的 from…let…where 片段

可选的 from…let…where 部分是查询主体的第一部分,可以由任意数量的3种子句构成—— from 子句、let 子句和 where 子句。

1)from 子句

查询表达式从必需的 from 子句开始,后面跟的是查询主体。主体本身可以从任何数量的其他 from 子句开始,每一个 from 子句都指定了一个额外的源数据集合并引入了要在之后运算的迭代变量。

请添加图片描述

示例:

      static void Main(string[] args)
        {
            var groupA = new[] { 3, 4, 5, 6 };
            var groupB = new[] { 6, 7, 8, 9 };

            var someInts = from a in groupA//必需的第一个 from 子句
                           from b in groupB//查询主体的第一个子句
                           where a > 4 && b <= 8
                           select new { a, b, sum = a + b };//匿名类型对象

            foreach (var x in someInts)
                Console.WriteLine(x);

            Console.ReadKey();
        }

输出结果:

{ a = 5, b = 6, sum = 11 }
{ a = 5, b = 7, sum = 12 }
{ a = 5, b = 8, sum = 13 }
{ a = 6, b = 6, sum = 12 }
{ a = 6, b = 7, sum = 13 }
{ a = 6, b = 8, sum = 14 }

2)let 子句

let 子句接受一个表达式的运算并且把它赋值给一个需要在其他运算种使用的标识符。

let 子句的语法如下:

let Identifier = Expression

示例:

static void Main(string[] args)
        {
            var groupA = new[] { 3, 4, 5, 6 };
            var groupB = new[] { 6, 7, 8, 9 };

            var someInts = from a in groupA
                           from b in groupB
                           let sum = a + b //在新的变量中保存结果
                           where sum == 12
                           select new { a, b, sum};

            foreach (var x in someInts)
                Console.WriteLine(x);

            Console.ReadKey();
        }

输出结果:

{ a = 3, b = 9, sum = 12 }
{ a = 4, b = 8, sum = 12 }
{ a = 5, b = 7, sum = 12 }
{ a = 6, b = 6, sum = 12 }

3)where 子句

==where 子句根据之后的运算来去除不符合指定条件的项目。==where 子句如下:

where BooleanExpression

有关 where的要点:

  • 只要是在 from…let…where 部分中,查询表达式可以有任意多个 where 子句。
  • 一个项必须满足所有 where 子句才能避免在之后被去除。
    static void Main(string[] args)
        {
            var groupA = new[] { 3, 4, 5, 6 };
            var groupB = new[] { 6, 7, 8, 9 };

            var someInts = from a in groupA
                           from b in groupB
                           let sum = a + b 
                           where sum >= 11//条件1
                           where a == 4 //条件2
                           select new { a, b, sum };

            foreach (var x in someInts)
                Console.WriteLine(x);

            Console.ReadKey();
        }

输出结果:

{ a = 4, b = 7, sum = 11 }
{ a = 4, b = 8, sum = 12 }
{ a = 4, b = 9, sum = 13 }

二、orderby 子句

orderby 子句接受一个表达式并根据表达式按顺序返回结果项

可选的 ascending 和 descending 关键字设置了排序方向。表达式通常是项的一个字段。该字段不一定非得是数值字段,也可以是字符串这样的可排序类型。

  • orderby 子句的默认排序是升序。然后,我们可以使用 ascending 和 descending 关键字显式地设置元素的排序为升序或降序
  • 可以有任意多个子句,它们必须使用逗号分隔。
       static void Main(string[] args)
        {
            var students = new[]
            {
              new { LName = "Jones", FName = "Mary" , Age = 19, Major = "History" },
              new { LName = "Smith", FName = "Bob" , Age = 20, Major = "CompSci" },
              new { LName = "Fleming", FName = "Carol" , Age = 21, Major = "History" },
          };

            var query = from student in students
                        orderby student.Age//根据年龄排序
                        select student;

            foreach (var s in query)
                Console.WriteLine($"{ s.LName },{ s.FName },{ s.Age },{ s.Major }");

            Console.ReadKey();
        }

输出结果:

Jones,Mary,19,History
Smith,Bob,20,CompSci
Fleming,Carol,21,History

三、select…group 子句

select…group 部分由两种类型的子句组成——select 子句和 group…by 子句。select…group 部分之前的子句指定了数据源和要选择的对象。

select…group 部分的功能如下:

  • 1)==select 子句指定应用选择所选对象的哪些部分。==它可以指定下面的任意一项。

    • 整个数据项。
    • 数据项的一个字段。
    • 数据项中几个字段组成的新对象。(或类似其他值)
  • 2)group…by 子句是可选的,用来指定选择的项如何被分组。

示例:

       static void Main(string[] args)
        {
            var students = new[]
            {
              new { LName = "Jones", FName = "Mary" , Age = 19, Major = "History" },
              new { LName = "Smith", FName = "Bob" , Age = 20, Major = "CompSci" },
              new { LName = "Fleming", FName = "Carol" , Age = 21, Major = "History" },
          };

            var query = from s in students
                        select s;

            foreach (var q in query)
                Console.WriteLine($"{ q.LName },{ q.FName },{ q.Age },{ q.Major }");

            Console.ReadKey();
        }

输出结果:

Jones,Mary,19,History
Smith,Bob,20,CompSci
Fleming,Carol,21,History

也可以使用 select 子句来选择对象的某些字段。

var query = from s in students
			select s.LName;

foreach(var q in query)
Console.WriteLine(q);

输出结果:

Jones
Smith
Fleming

四、查询中的匿名类型

查询结果可以由原始集合的项、原始集合中项的字段或匿名类型组成。

select new { s.LastNmae, s.FirstName, s.Major };

示例:

       static void Main(string[] args)
        {
            var students = new[]
            {
              new { LName = "Jones", FName = "Mary" , Age = 19, Major = "History" },
              new { LName = "Smith", FName = "Bob" , Age = 20, Major = "CompSci" },
              new { LName = "Fleming", FName = "Carol" , Age = 21, Major = "History" },
          };

            var query = from s in students
                        select new { s.LName, s.FName, s.Major };

            foreach (var q in query)
                Console.WriteLine($"{ q.LName },{ q.FName } -- { q.Major }");

            Console.ReadKey();
        }

//创建匿名类型:s.LName, s.FName, s.Major
//匿名类型的访问字段:q.LName、q.FName、q.Major

输出结果:

Jones,Mary – History
Smith,Bob – CompSci
Fleming,Carol – History

五、group 子句

group 子句根据指定的标准对选择的对象进行分组。

有关 group 子句的要点:

  • 如果项包含在查询的结果中,它们就可以根据某个字段的值进行分组。作为分组依据的属性叫键(key)。

  • group 子句返回的不是原始数据源中项的枚举,而是返回可以枚举已经形成的项的分组的可枚举类型。

  • 分组本身是可枚举类型,它们可以枚举实际的项。

group student by student.Majot;

示例:

      static void Main(string[] args)
        {
            var students = new[]
            {
              new { LName = "Jones", FName = "Mary" , Age = 19, Major = "History" },
              new { LName = "Smith", FName = "Bob" , Age = 20, Major = "CompSci" },
              new { LName = "Fleming", FName = "Carol" , Age = 21, Major = "History" },
          };

            var query = from student in students
                        group student by student.Major;

            foreach (var g in query)//枚举分组
            {
                Console.WriteLine("{0}", g.Key);

                foreach (var s in g)//枚举分组中的项
                    Console.WriteLine($"   { s.LName },{ s.FName }");
            }

            Console.ReadKey();
        }

//分组键:g.Key

输出结果:

History
Jones,Mary
Fleming,Carol
CompSci
Smith,Bob

图20-10演示了从查询表达式返回并保存于查询变量中的对象。

  • 从查询表达式返回的对象是从查询中枚举分组结果的可枚举类型。
  • 每一个分组由一个叫作键的字段区分。
  • 每一个分组本身是可枚举类型并且可以枚举它的项。

请添加图片描述

六、查询延续:into 子句

查询延续子句可以接受查询的一部分的结果并赋予一个名字,从而可以在查询的另一部分中使用。

示例:

        static void Main(string[] args)
        {
            var groupA = new[] { 3, 4, 5, 6 };
            var groupB = new[] { 4, 5, 6, 7 };

            var someInts = from a in groupA
                           join b in groupB on a equals b
                           into groupAandB //查询延续
                           from c in groupAandB
                           select c;

            foreach (var v in someInts)
                Console.Write($"{ v } ");

            Console.ReadKey();
        }

输出结果:

4 5 6

<think>嗯,用户之前问过关于Unity UI层级的问题,现在又转向了C#LINQ表达式树。可能他们正在学习Unity开发,同时需要处理C#中的数据查询,或者是在优化代码时遇到了问题。用户可能是中级开发者,已经有一定的C#基础,但对LINQ的高级特性还不熟悉,尤其是表达式树。 我需要先确定用户对LINQ的基本了解程度。LINQ表达式树和普通的LINQ查询有什么区别呢?表达式树(Expression Trees)允许在运行时将代码表示为数据结构,这样可以在执行前进行分析或转换,比如转换为SQL查询。用户可能在使用Entity Framework时遇到过延迟执行或IQueryable接口,这时候表达式树就派上用场了了。 用户可能的具体需求是什么?他们可能在尝试动态构建查询条件,或者在写一些需要解析表达式的高级库。比如动态生成Where条件,或者将C#表达式转换为其他语言(如SQL)。这时候表达式树就比普通的委托更合适,因为委托是编译后的代码,无法被分析。 接下来要考虑如何组织回答结构。先解释表达式树的基本概念,然后和普通LINQ做对比,再讲构造和解析方法,最后给出应用场景和示例。这样用户能逐步理解,从基础到应用。 要注意用户可能混淆Expression和Func的区别,需要明确说明表达式树是数据结构,而Func是编译后的委托。同时,举一些实际的例子,比如动态查询构造,或者与EF Core结合使用的情况,这样用户能更好地理解实际用途。 还要注意潜在的问题,比如表达式树的性能开销,或者构造复杂表达式时的注意事项。比如,不是所有的C#语法都能转换为表达式树,某些操作可能不支持,这可能会让用户遇到运行时错误。 最后,确保语言通俗易懂,避免过多术语堆砌,用代码示例辅助说明。比如展示如何手动构建一个简单的表达式树,或者如何将lambda表达式转换为表达式树进行解析。这样用户能直观看到表达式树的结构和操作方式。</think>以下是对C# LINQ表达式树的系统解析: --- ### 一、核心概念 #### 1. 表达式树本质 - **数据结构**:将代码逻辑表示为树形结构 - **运行时分析**:可在执行前被解析、修改或转换 - **延迟执行**:与`IQueryable`配合实现跨平台查询(如SQL转换) #### 2. 与Lambda表达式的区别 ```csharp // 普通Lambda(编译为委托) Func<Student, bool> func = s => s.Age > 18; // 表达式树(保留结构信息) Expression<Func<Student, bool>> expr = s => s.Age > 18; ``` --- ### 二、关键操作 #### 1. 表达式树构造 ```csharp // 手动构建 x => x.Age > 18 ParameterExpression param = Expression.Parameter(typeof(Student), "x"); MemberExpression property = Expression.Property(param, "Age"); ConstantExpression constant = Expression.Constant(18); BinaryExpression body = Expression.GreaterThan(property, constant); Expression<Func<Student, bool>> expr = Expression.Lambda<Func<Student, bool>>(body, param); ``` #### 2. 表达式解析 ```csharp void ParseExpression(Expression expr) { switch (expr.NodeType) { case ExpressionType.Lambda: var lambda = (LambdaExpression)expr; ParseExpression(lambda.Body); break; case ExpressionType.GreaterThan: var binary = (BinaryExpression)expr; Console.WriteLine($"操作符: {binary.NodeType}"); ParseExpression(binary.Left); ParseExpression(binary.Right); break; case ExpressionType.MemberAccess: var member = (MemberExpression)expr; Console.WriteLine($"访问属性: {member.Member.Name}"); break; case ExpressionType.Constant: var constant = (ConstantExpression)expr; Console.WriteLine($"常量值: {constant.Value}"); break; } } ``` --- ### 三、典型应用场景 #### 1. 动态查询构建 ```csharp // 根据条件动态组合Where IQueryable<Student> query = dbContext.Students; if (filterByAge) { Expression<Func<Student, bool>> ageExpr = s => s.Age > minAge; query = query.Where(ageExpr); } if (filterByName) { Expression<Func<Student, bool>> nameExpr = s => s.Name.Contains(searchName); query = query.Where(nameExpr); } ``` #### 2. SQL转换(EF Core原理) ```csharp // 表达式树会被转换为SQL var query = db.Products .Where(p => p.Price > 100) .OrderBy(p => p.Name) .Select(p => new { p.Id, p.Name }); // 生成的SQL: // SELECT Id, Name FROM Products WHERE Price > 100 ORDER BY Name ``` #### 3. 规则引擎实现 ```csharp // 组合业务规则 Expression<Func<Order, bool>> expr = order => order.Total > 1000 && order.Items.Any(item => item.Category == "Electronics"); // 可动态添加新规则 if (needVipCheck) { expr = expr.And(order => order.User.IsVip); } ``` --- ### 四、性能优化要点 1. **缓存机制** 频繁使用的表达式应缓存`Compile()`结果: ```csharp private static Func<Student, bool> _cachedFilter; public void FilterStudents(IEnumerable<Student> students) { _cachedFilter ??= expr.Compile(); var result = students.Where(_cachedFilter); } ``` 2. **表达式合并** 使用第三方库(如`PredicateBuilder`)优化复杂表达式: ```csharp var predicate = PredicateBuilder.New<Product>(true); if (priceFilter) predicate = predicate.And(p => p.Price < maxPrice); if (stockFilter) predicate = predicate.And(p => p.Stock > 0); ``` 3. **避免过度复杂化** 嵌套超过3层的表达式树会增加SQL转换失败概率 --- ### 五、调试技巧 #### 1. 可视化工具 - 使用DebugView属性查看表达式结构: ```csharp Console.WriteLine(expr.DebugView); /* 输出: .Lambda #Lambda1<System.Func`2[Student,System.Boolean]>(Student $s) { $s.Age > 18 } */ ``` #### 2. ExpressionTreeVisualizer(VS插件) - 在Visual Studio中直接图形化查看表达式树结构 --- ### 六、限制与注意事项 1. **不完全支持** - 部分C#语法无法转换为表达式树(如:多行lambda、某些运算符) 2. **执行方式差异** ```csharp // LINQ to Objects(内存查询) var list = students.Where(s => s.Age > 18).ToList(); // 立即执行 // LINQ to Entities(数据库查询) var query = dbContext.Students.Where(s => s.Age > 18); // 生成表达式树 ``` 3. **动态程序集限制** 动态生成的表达式树无法在iOS IL2CPP环境下使用 --- **最佳实践**:在需要跨平台查询转换或动态逻辑构建时使用表达式树,内存操作优先使用普通LINQ。对于复杂表达式,建议结合[ExpressionVisitor](https://docs.microsoft.com/en-us/dotnet/api/system.linq.expressions.expressionvisitor)类实现定制化处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值