C#:Lambda/Linq

发布时间 2024-01-08 20:46:38作者: 52Hertz程序人生

文章目录

Linq和Lambda简介

匿名类

匿名类+object

匿名类+dynamic

匿名类+var

Lambda表达式

Lambda是什么

Lambda的演变过程

Lambda的优势

扩展方法(Extension Method)

表达式树(Expression Tree)

生成、编译和执行表达式树

Expression<TDelegate>和代码分析

生成表达式树的一个情景化的例子

Linq

Linq是什么

Linq的原理

Linq的优势

Linq写查询的两种形式

Linq的延迟计算

Linq和Lambda语句示例

简单的查询语句

带where的查询

"count,min,max,sum" 函数语句

数据排序order by desc/asc语句

top(1)语句

跳过前N条取余下的数据 Skip语句

截取指定长度的数据 Take语句

分页查询语句

分组group by语句

包含Contains , 类似like ‘%%’语句

Sql中的In – 通过Contains实现

内连接 Inner Join

Linq和Lambda实现 左链接、右链接、内链接

交集、并集、差集

Linq和Lambda简介

 Lambda 表达式 是一种轻量级的匿名函数,允许你方便地表示可传递给委托类型的代码块。Lambda表达式可以在 LINQ 查询、委托的方法体内等地方使用。

它的基本形式是 (input parameters) => expression, 这个表达式可以代表一个方法。例如:

Func<int, int, int> add = (x, y) => x + y;
int result = add(3, 5); // 结果为 8

LINQ (Language Integrated Query) 是一种用于查询各种数据源的技术,它允许以类似于 SQL 的方式来查询对象、集合和其他数据源。它提供了一组方法和语法,让你可以在数据源中执行各种查询操作,

如过滤、排序、投影和分组等。LINQ 与 Lambda 表达式一起使用,使得对数据的操作更加简洁和易读。例如:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();

在这个例子中,Where 是 LINQ 方法,n => n % 2 == 0 就是一个 Lambda 表达式,用于筛选出 numbers 列表中的偶数。

Lambda 表达式允许你定义简洁的匿名函数,而 LINQ 则是一种强大的查询语言,与 Lambda 表达式结合使用,使得在 C# 中对数据的查询和操作变得更加方便和直观。

 

匿名类

匿名类是在编程中用于临时封装数据的一种特殊类。它允许你在不事先定义类的情况下创建一个对象,并且通常用于临时性或简单的数据封装需求。

在 C# 中,匿名类是通过 new 关键字和对象初始化器语法来创建的,NETFramework3.0出现的。它的定义方式如下:

var person = new { Name = "John", Age = 30 };

在例子中,person 是一个匿名类的实例。该类有两个属性:NameAge。编译器会自动推断属性的类型基于初始化值的类型。

匿名类常用于 LINQ 查询中,用于选择特定的属性或形成临时的投影。例如:

var people = new[]
{
    new { Name = "Alice", Age = 25 },
    new { Name = "Bob", Age = 30 },
    new { Name = "Charlie", Age = 28 }
};

var names = people.Select(p => p.Name).ToList();

在例子中,people 是一个包含匿名类对象的数组。Select 方法用于选择每个对象的 Name 属性并将其组成一个新的列表。

匿名类通常用于简单的临时需求,因为它们没有类名,也无法被重用。它们对于快速封装和使用临时数据非常有用,但如果需要更复杂的逻辑或重复使用,则可能需要定义具名类。

匿名类+object

object去接匿名类,无法访问属性值,因为C#是强类型语言,object是在编译时确定类型,因为Object没有这个属性

object model = new  
{
    Id = 1,
    Name = "张三",
    Age = 30,
    ClassId = 2
};
//无法访问属性值
//model.Id = 134;
//Console.WriteLine(model.Id);

匿名类+dynamic

dynamic(动态类型)可以避开编译器检查,.NETFramework 4.0出现的
dynamic去接匿名类,可以访问属性值,因为dynamic是运行时才检查的,但是访问不存在的属性也不报错,运行时才报异常

dynamic dModel = new
{
    Id = 1,
    Name = "张三",
    Age = 30,
    ClassId = 2
};
//可以访问属性值
dModel.Id = 134;
Console.WriteLine(dModel.Id);
//但是访问不存在的属性也不报错,运行时才报异常
dModel.abccc = 1234;

匿名类+var

var去接匿名类,可以读取属性,不能给属性重新赋值,只能在初始化的时候给定一个值
var是编译器的语法糖,由编译器自动推算类型
var声明的变量必须初始化,必须能推算出类型,var aa = null;或者var aa;都是不正确的
var缺陷:阅读麻烦,建议能明确类型的还是明确类型,优点:简化代码

var vmodel = new
{
    Id = 1,
    Name = "张三",
    Age = 30,
    ClassId = 2
};
//不能给属性重新赋值
//vmodel.Id = 134;
//可以读取属性
Console.WriteLine(vmodel.Id);

 

Lambda表达式

Lambda是什么

(1)形如:()=> { } 就是lambda表达式

(2)lambda表达式就是一个匿名方法,在底层会生成在一个"<>"类中,生成带有名称的方法

Lambda的演变过程

(1).Netframework1.0/1.1,原始方法

/// <summary>
/// 声明委托
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public delegate void NoReturnWithPara(int x, string y);

/// <summary>
/// 声明方法
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
private void PrintParam(int x, string y)
{
    Console.WriteLine(x);
    Console.WriteLine(y);
}
NoReturnWithPara method = new NoReturnWithPara(PrintParam);

(2).NetFramework2.0,匿名方法

增加了一个delegate关键字,可以访问到除了参数以外的局部变量

int i = 0;
NoReturnWithPara method = new NoReturnWithPara(delegate (int x, string y)
{
    Console.WriteLine(x);
    Console.WriteLine(y);
    Console.WriteLine(i);
});

(4).NetFramework3.0后期,简化参数类型

去掉了匿名方法中的参数类型,这个是编译器提供的语法糖,编译器可以根据委托类型定义的参数类型推导出参数类型

int i = 0;
NoReturnWithPara method = new NoReturnWithPara((x, y) =>
{
    Console.WriteLine(x);
    Console.WriteLine(y);
    Console.WriteLine(i);
});

(5)如果方法体中只有一行代码,可以省略方法体大括号

NoReturnWithPara method = (x, y) => Console.WriteLine(x);

(6)如果方法只有一个参数,省略参数小括号

Action<string> method = x => Console.WriteLine(x);

(7)如果方法体中只有一行代码,且有返回值,可以省略return

Func<int, string> method = i => i.ToString();

Lambda的优势

1. 简洁性和清晰度:Lambda 表达式使得编写短小精悍的匿名函数变得更容易。它可以在一行代码内表达简单的功能,减少了传统函数定义所需的模板代码,让代码更为精简。

2. 便捷性:Lambda 表达式可以直接作为参数传递给方法,这种便捷性特别适用于需要传递函数作为参数的场景,如 LINQ 查询、委托或事件处理等。

3. 闭包和延迟执行:Lambda 表达式可以捕获外部变量,形成闭包,使得它们可以在其定义范围之外使用这些变量。此外,Lambda 表达式通常用于实现延迟执行的功能,只有在需要时才会被执行,有助于提高性能和资源利用率。

4. 函数式编程:Lambda 表达式推动了函数式编程风格在 C# 中的发展。它们可以与 LINQ 配合使用,以声明性的方式处理集合和序列,使得代码更具表现力和功能性。

5. 提高可读性和可维护性:当使用合适的命名和结构时,Lambda 表达式可以提高代码的可读性。在一些情况下,将复杂的操作转化为 Lambda 表达式可以让代码更易于理解和维护。

6. 并行性和异步编程:Lambda 表达式与并行编程和异步编程结合使用时,可以轻松地实现并行操作和异步任务,提高程序的性能和响应性。

Lambda 表达式是一种强大的工具,它在 C# 中提供了更简洁、更灵活和更具表现力的方式来表达功能。它们的使用使得代码更为精简、可读,并且能够更好地适应现代软件开发中的需求。

 

扩展方法(Extension Method)

扩展方法的使用十分广泛,包括LINQ的查询扩展方法也是基于扩展方法。

扩展方法提供了在类型定义之外任意位置扩展类型的功能能力。

扩展方法可用于类、结构、枚举等。

定义扩展方法,需要满足:

  • 扩展方法的只能定义在static类中,且自身必须是static方法
  • 使用this关键词修饰参数,并且this参数只能放在第一个,它标识出是该方法是哪个类型的扩展方法

扩展方法定义如下:

public static ArticleExtensions
{
    public static int WordCount(this Article article)  //定义Article类的扩展方法WordCount,用于数出单词数量
    {
        return article.Content.Split(new char[] { ' ', '.', '?', '!' },
            StringSplitOptions.RemoveEmptyEntries).Length;
    }
}

此定义不在Article内,而是在独立的静态类ArticleExtensions中。这样定义后,Article类的对象便拥有了WordCount这个扩展方法可用。

事实上,单个静态类中,可以定义多个不同类型的扩展方法。

使用扩展方法,可以像使用类型的方法一样:对象.扩展方法(参数列表)

上例中,可以这样用:

Article article = new Article("A Story.txt");
var wordCount = article.WordCount();  //调用扩展方法,调用者本身作为参数列表里的被扩展的对象(即第一个参数)传入方法,因此调用时括号内会少了第一个参数。

需要注意,需要引用扩展方法所在的命名空间后,才可以使用该扩展方法。

扩展方法也能当作一般的静态方法使用:

Article article = new Article("A Story.txt");
var wordCount = ArticleExtensions.WordCount(article);  //当作一般静态方法使用

扩展方法也支持泛型。例如我们可以实现一个泛型扩展方法ToJson<T>,T类型作为被扩展的类型。之后便可以使用任意对象.ToJson()将对象转换成Json字串(这个例子中有个细节:ToJson的泛型参数也被省略了,因为调用者就是第一个参数,可以推断出泛型类型)。

另外,扩展方法如果跟既有方法同名,由于静态方法只能是隐藏,无法作为重写,所以:

  • 扩展方法与类型自身方法冲突时:将会优先使用自身方法
  • 祖先类型拥有相同签名的扩展方法:由于会进行方法隐藏,将使用对象所显式声明类型的扩展方法,而不是实际类型的方法。这适用于各种情况:实际类型拥有自身定义的静态的或非静态的同签名方法;实际类型拥有同签名的扩展方法。这些情况下都不会使用实际类型的方法。
  • 多处定义多个同签名的扩展方法(只要不在一个类中定义,编译器便不会报错):如果它们在不同的命名空间中,使用时可以只using需要使用的扩展方法所在的命名空间,可以正常调用。如果它们在同一个命名空间下,或者使用时同时using了这两个命名空间,则在调用时会引起二义性错误。此时请直接用普通静态方法的语法来调用它们。

扩展方法(Extension Methods)是C#中一种强大的特性,允许开发者在不修改原始类代码的情况下,向现有的类添加新的方法。

 

表达式树(Expression Tree)

表达式树,主要提供两种能力:

  • 可以在运行时动态生成可编译和执行的表达式
  • 可以在运行时分析处理代码中编写的表达式

生成、编译和执行表达式树

对于下列简单的表达式

int sum = 1 + 2

它具体由以下部分组成:

  • 左值部分
  • 变量类型声明 int
  • 变量名 sum
  • 赋值符号 =
  • 右值部分
  • 左操作数 1
  • 加法符号 +
  • 右操作数 2

学过算法的同学应该都知道,我们书写的符合自然语义的表达式为中缀表达式,它无法直接被计算机处理。一般在计算表达式时,需要将中缀表达式转成后缀表达式,这就会用到二叉树。表达式可以表示成一棵二叉树,树的非叶子节点为表达式的运算符,叶子节点为操作数;节点的左侧子节点为左操作数,右侧子节点为右操作数。

 上述表达式,如果用C#的表达式树来描述:

var varibleSum = Expression.Variable(typeof(int), "sum"); //声明int类型的变量sum
var one = Expression.Constant(1);  //声明左操作数常量表达式 1
var two = Expression.Constant(2);  //声明右操作数常量表达式 2
var add = Expression.Add(one, two); //声明将两个常量表达式加起来的表达式 1+2
var assignment = Expression.Assign(varibleSum, add); //声明赋值表达式

表达式树与上述二叉树的结构一致。因此构建的方式是先构建叶子节点,然后再通过运算符连接叶子节点构成一个个森林,依次进行下去直到合并成一棵树。

需要注意的是,表达式树是不可变的。一旦生成后,你就无法修改它了。如果需要修改,请考虑复制一棵表达式树,并在复制的过程中替换你要修改的部分。

表达式树是可以被执行的。只需要

  • 用Expression的Lamdba方法来生成一个Lambda表达式的表达式树,以便封装成一个可调用的方法。
  • 编译它
  • 执行

Expression的Lamdba方法返回一个Expression\<TDelegate>类型的表达式,它可以被编译为一个方法。因此你也可以将它分配给一个委托变量。

var expFunc = Expression.Lambda<Func<int>>(assignment); //生成Lambda,expFunc的类型是Expression<Func<int>>
var func = expFunc.Compile(); //编译,并赋值给委托。func的类型是Func<int>
var assignResult = func();  //执行委托,assignResult为int sum = 1 + 2赋值表达式的结果,为3

Expression<TDelegate>和代码分析

Expression\<TDelegate>它派生自LambdaExpression,用于描述一个Lambda的表达式。其中TDelegate虽然未指定泛型约束条件(主要原因是C#7.1之前并不支持Delegate作为泛型约束类型),但是一般为强类型委托类型(Func或者Action)。

该类型的对象可以由下列方式创建:

  • 使用Expression.Lambda静态方法构建
  • 直接使用表达式Lambda进行赋值,但是有限制:
  • 只能是“表达式Lambda”,不能是“语句Lambda”
  • 这种赋值要求表达式不能包含赋值运算符=

直接赋值为表达式Lambda时,编译器会自动将你的Lambda表达式内容转换成表达式树。开发者因此可以读取表达式树的每个节点,从而进行代码分析。也基于此,表达式树并不仅仅是类库层面的更新,同样也是语法级别的升级。

表达式1 + 2,如果直接用表达式Lambda赋值为Expression<TDelegate>,则可以写作:

Expression<Func<int>> funcExp = () => 1 + 2;
var rsl = funcExp.Compile()();  //rsl为 3

编译器自动生成了() => 1 + 2的表达式树,上面的代码等同于:

Expression<Func<int>> funcExp = Expression.Lambda<Func<int>> (
    Expression.Add(
        Expression.Constant(1, typeof(int)),
        Expression.Constant(2, typeof(int))
    )
);
var rsl = funcExp.Compile()(); //rsl为 3

生成表达式树的一个情景化的例子

想象一下你有一个需求,需要根据给定的条件动态地构建一个查询,比如筛选一组数据并返回符合特定条件的结果。在这个例子中,我们将使用 Expression<TDelegate> 来构建一个简单的表达式树来表示这个查询条件。

假设有一个 Person 类:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

现在,我们希望动态地创建一个查询条件,找出年龄大于某个特定值的人。我们可以使用表达式树来实现这个查询条件的动态构建。

using System;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        // 定义参数
        ParameterExpression parameter = Expression.Parameter(typeof(Person), "p");

        // 定义条件:p.Age > 30
        Expression property = Expression.Property(parameter, "Age");
        Expression constant = Expression.Constant(30, typeof(int));
        BinaryExpression comparison = Expression.GreaterThan(property, constant);

        // 构建 Lambda 表达式
        Expression<Func<Person, bool>> lambda = Expression.Lambda<Func<Person, bool>>(comparison, parameter);

        // 编译表达式树并使用
        Func<Person, bool> func = lambda.Compile();

        // 测试查询条件
        var people = new[]
        {
            new Person { Name = "Alice", Age = 25 },
            new Person { Name = "Bob", Age = 35 },
            new Person { Name = "Charlie", Age = 40 }
        };

        var result = people.Where(func);

        // 输出结果
        foreach (var person in result)
        {
            Console.WriteLine($"{person.Name}, Age: {person.Age}");
        }
    }
}

这个例子中,我们使用 Expression 类的方法来构建了一个简单的表达式树,表示了一个年龄大于 30 的查询条件。然后我们将这个表达式树编译为一个委托,并在一组人员数据上应用这个查询条件。最后,我们打印出满足条件的人员信息。

这种动态构建查询条件的方式非常有用,特别是在需要根据不同的条件来过滤数据时。通过使用表达式树,我们可以以编程的方式构建灵活的查询,而不需要在每个条件变化时都手动编写不同的查询逻辑。

 

Linq

Linq是什么

LINQ(Language Integrated Query)是C#和.NET框架中的一种特性,它提供了一种统一的编程模型,用于查询各种数据源,如集合、数据库、XML等。

它允许开发者使用类SQL的查询语法(查询表达式)或方法调用的方式来对数据进行查询、过滤、排序、投影等操作。

(1)Linq(Language Integrated Query)即语言集成查询。
(2)Linq是一组语言特性和API,使得你可以使用统一的方式编写各种查询。用于保存和检索来自不同数据源的数据,从而消除了编程语言和数据库之间的不匹配,以及为不同类型的数据源提供单个查询接口。
(3)Linq总是使用对象,因此你可以使用相同的查询语法来查询和转换XML、对象集合、SQL数据库、ADO.NET数据集以及任何其他可用的LINQ提供程序格式的数据。
(4)Linq主要包含以下部分

  • Linq to Objects 主要负责对象的查询。
  • Linq to XML 主要负责XML的查询。
  • Linq to ADO.NET 主要负责数据库的查询。
  • Linq to SQL
  • Linq to DataSet
  • Linq to Entities
  • Linq to Everything

Linq的原理

LINQ 的原理主要基于两个重要的组成部分:语言集成和提供提供者模型。

语言集成:

LINQ 是 C# 语言的一部分,其核心是将查询能力直接集成到编程语言中。这使得可以使用类SQL的查询语法或方法调用的方式来对数据进行查询和操作。在编写代码时,可以像操作集合一样操作数据源,

无需使用额外的查询语言或 API。

提供者模型(Provider Model):

LINQ 使用提供者模型来实现对不同数据源的查询。LINQ 提供了一组标准的接口,以及抽象类和方法,允许针对特定数据源实现自定义的提供者。这些提供者将 LINQ 查询翻译成适合特定数据源的查询语言或命令(如 SQL、XML查询语言等),并执行这些查询。这种模型使得 LINQ 能够在各种数据源上工作,包括对象集合、数据库、XML 等。

 LINQ 提供者的主要组成部分:

1. 查询提供者(Query Provider):这是 LINQ 的核心部分,负责接收 LINQ 查询并将其转换为目标数据源的查询。例如,对于数据库查询,有 Entity Framework 提供了 LINQ to SQL 提供者,将 LINQ 查询转换为 SQL 查询。

2. 表达式树(Expression Trees):LINQ 查询在编译时被转换为表达式树。表达式树是一个动态表示代码的树状结构,它代表了查询语句中的各个部分,如过滤、排序、投影等,这些表达式树可以被提供者解析并转换为目标查询语言。

3. 标准查询操作符(Standard Query Operators):LINQ 提供了一组标准的查询操作符(例如 `Where`、`Select`、`OrderBy` 等),这些操作符是对 LINQ 查询的构建块,使得可以构建灵活的查询表达式。

查询执行过程:

1. 查询构建阶段:在编写 LINQ 查询时,查询不会立即执行,而是被解释为表达式树。

2. 表达式树解析阶段:查询提供者接收到表达式树后,会解析表达式树,将其翻译成目标数据源能够理解的查询语言(如 SQL)或命令。

3. 查询执行阶段:解析后的查询语言或命令被传递给目标数据源执行,数据源执行查询并返回结果。

这个流程说明了 LINQ 如何将查询表达式转换为特定数据源的查询命令,并最终执行这些命令来获取所需的结果。通过语言集成和提供者模型的组合,LINQ 提供了一种统一的、方便的方式来查询和操作各种数据源。

需求:存在一个集合,要过滤其中的数据

(1)方案1:循环 + 判断

//要求查询Student中年龄小于30的; 
List<Student> studentList = this.GetStudentList();
List<Student> list = new List<Student>();
foreach (var item in studentList)
{
    if (item.Age < 30)
    {
        list.Add(item);
    }
}
//要求Student名称长度大于2
List<Student> list2 = new List<Student>();
foreach (var item in studentList)
{
    if (item.Name.Length > 2)
    {
        list2.Add(item);
    }
}
//N个条件叠加
List<Student> list3 = new List<Student>();
foreach (var item in studentList)
{
    if (item.Id > 1
        && item.Name != null
        && item.ClassId == 1
        && item.Age > 20)
    {
        list.Add(item);
    }
}

(2)方案2:扩展方法 + Lambda

可以把不变的业务逻辑保留,把可变的,不固定的业务逻辑转移出去,就可以用委托包装一个方法传递过来,简化重复代码

/// <summary>
/// 泛型扩展方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="oldlist"></param>
/// <param name="func"></param>
/// <returns></returns>
public static List<T> CustomWhere<T>(this List<T> oldlist, Func<T, bool> func)
{
    List<T> newlist = new List<T>();
    foreach (var item in oldlist)
    {        
        if (func.Invoke(item))
        {
            newlist.Add(item);
        }
    }
    return newlist;
}
//要求查询Student中年龄小于30的; 
List<Student> studentList = this.GetStudentList();
List<Student> list = studentList.CustomWhere(item => item.Age < 30);
//要求Student名称长度大于2
List<Student> list2 = studentList.CustomWhere(item => item.Name.Length > 2);
//N个条件叠加
List<Student> list3 = studentList.CustomWhere(item => item.Id > 1
        && item.Name != null
        && item.ClassId == 1
        && item.Age > 20);

(3)方案3:Linq中的Where

  • Linq实现原理和我们自己写的扩展方法类似
  • Linq的底层都是通过迭代器来实现就是支持循环
  • Linq的底层使用IEnumerable来承接数据
//来自于Linq的Where实现
public static IEnumerable<TSource> Where<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
    }
    if (predicate == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate);
    }
    Iterator<TSource> iterator = source as Iterator<TSource>;
    if (iterator != null)
    {
        return iterator.Where(predicate);
    }
    TSource[] array = source as TSource[];
    if (array != null)
    {
        if (array.Length != 0)
        {
            return new WhereArrayIterator<TSource>(array, predicate);
        }
        return Empty<TSource>();
    }
    List<TSource> list = source as List<TSource>;
    if (list != null)
    {
        return new WhereListIterator<TSource>(list, predicate);
    }
    return new WhereEnumerableIterator<TSource>(source, predicate);
}
//要求查询Student中年龄小于30的; 
List<Student> studentList = this.GetStudentList();
List<Student> list = studentList.Where(item => item.Age < 30).ToList();
//要求Student名称长度大于2
List<Student> list2 = studentList.Where(item => item.Name.Length > 2).ToList();
//N个条件叠加
List<Student> list3 = studentList.Where(item => item.Id > 1
        && item.Name != null
        && item.ClassId == 1
        && item.Age > 20).ToList();

Linq的优势

1. 减少编码:相比较传统的方式,LINQ减少了要编写的代码量。

2. 可读性强:LINQ增加了代码的可读性,开发人员可以很轻松地理解和维护。

3. 标准化的查询方式可以使用相同的LINQ语法查询多个数据源。

4. 智能感知提示LINQ为通用集合提供智能感知提示。

5. 统一的查询语法:LINQ 提供了类SQL的查询语法,使得对于不同数据源(如集合、数据库、XML等)的查询具有统一的语法和风格。这种统一性简化了代码,降低了学习成本,并提高了可读性。

6. 类型安全:LINQ 是强类型的,这意味着编译器在编译时可以检查查询的正确性,避免了一些在运行时才能发现的错误。这种类型安全性提高了代码的质量和稳定性。

7. 编译时错误检查:LINQ 查询在编写时被转换为表达式树,这使得编译器可以在编译时检查查询表达式的正确性,有助于发现和修复错误。

8.集成性:LINQ 是 C# 语言的一部分,可以直接嵌入到代码中。这种集成性使得可以更自然地进行数据查询和处理,无需切换到其他语言或工具。

9. 可组合性:LINQ 查询是可组合的,允许通过链式操作构建复杂的查询。这使得可以在不同的查询条件下动态构建查询,使代码更为简洁和灵活。

10. 适用范围广泛:LINQ 可以用于多种数据源,如对象集合、数据库(通过 Entity Framework、LINQ to SQL)、XML 等。这使得相同的查询技术可以应用于不同的数据存储系统。

11. 更高的抽象级别: LINQ 查询操作提供了更高的抽象级别,可以更直观地表达查询意图,而不必关注底层的实现细节。这使得代码更易读、易维护。

总体来说,LINQ 提供了一种强大且统一的方式来查询和操作数据,它的类型安全性、集成性、可读性和灵活性使得在处理数据时更为便捷和高效。

Linq写查询的两种形式

(1)查询语法

使用标准的方法调用,这些方法是一组叫做标准查询运算符的方法

(2)方法语法

看上去和SQL语句很相似,使用查询表达式形式书写。微软推荐使用查询语法,因为它更易读

在编译时,CLR会将查询语法转换为方法语法

int[] num = { 2, 4, 6, 8, 10 };
var numQuery = from number in num //查询语法
               where number < 8
               select number;
var numMethod = num.Where(x => x < 8); //方法语法

Linq的延迟计算

一般步骤:获取数据源、创建查询、执行查询。

需要注意的是,尽管查询在语句中定义,但直到最后的foreach语句请求其结果的时候才会执行。

int[] number = { 2, 4, 6, 8, 10 }; //获取数据源
IEnumerable<int> lowNum = from n in number //创建并存储查询,不会执行操作
                          where n < 8
                          select n;
foreach (var val in lowNum) //执行查询
{
    Console.Write("{0} ", val);
}

 

Linq和Lambda语句示例

1.简单的查询语句

// Linq语法:
  var data=from a in db.Areas select a ;
// Lamda语法:
  var data=db.Areas;
// sql语法:
  string sqlStr=" SELECT * FROM Areas  ";

2.带where的查询

// Linq语法:
  var data=from a in db.orderInfo where a.orderId > 20 select a ;
// Lamda语法:
  var data=db.orderInfo.Where( t=>t.orderId > 20 ) ;
// sql语法:
  string sqlStr=" SELECT * FROM orderInfo WHERE orderId > 20 ";

3."count,min,max,sum" 函数语句

// Linq语法:
  var data=( from a in db.orderInfo select a ).Max( p=>p.orderId ) ;//查询该表中最大编号Id
  var data=( from a in db.orderInfo select a ).Min( p=>p.orderId ) ;//查询该表中最小编号Id
  var data=( from a in db.orderInfo select a ).Count() ;//查询该表数据总条数
  var data=( from a in db.orderInfo select a ).Sum( p=>p.orderMoney ) ;//查询该表中所有消费额的总数(求和)
  
// Lamda语法:
  var data=db.orderInfo.Max( t=>t.orderId );//查询该表中最大编号Id
  var data=db.orderInfo.Min( t=>t.orderId );//查询该表中最小编号Id
  var data=db.orderInfo.Count();//查询该表数据总条数
  var data=db.orderInfo.Sum( t=>t.orderMoney );//查询该表中所有消费额的总数(求和)
  
// sql语法:
  string sqlStr=" SELECT MAX(orderId) FROM orderInfo ";
  string sqlStr=" SELECT MIN(orderId) FROM orderInfo ";
  string sqlStr=" SELECT COUNT(*) FROM orderInfo ";
  string sqlStr=" SELECT SUM(orderMoney ) FROM orderInfo ";

4.数据排序order by desc/asc语句

// Linq语法:
  var data=from a in db.orderInfo where a.orderId > 20 orderby a.orderId descending select a ;//倒序排序,升序可用ascending关键字
  
// Lamda语法:
  // 情况一,根据单字段排序: OrderByDescending
  var data=db.orderInfo.OrderByDescending( t=>t.orderId ).Where( t=>t.orderId > 20 ) .ToList();// 升序可用OrderBy关键字
  // 情况二,根据多字段主次排序:OrderBy == ThenBy
  var priceMonthEntities = priceMonthApp.GetList().OrderBy(t => t.F_Year).ThenBy(t => t.F_Month).ToList();//先按年升序,再按月升序
  
// sql语法:
  string sqlStr=" SELECT * FROM orderInfo WHERE orderId > 20 ORDER BY orderId DESC  ";//倒序排序,升序可用ASC关键字

5.top(1)语句

// Linq语法:  取集合中第一个记录
var ss = (from r in db.Am_recProScheme
          select r).FirstOrDefault();
// Lamda语法: 
var ss1 = db.Am_recProScheme.FirstOrDefault();
//var ss1 = db.Am_recProScheme.First();   
// Sql语法:       
string sssql = "select top(1) * from Am_recProScheme";


//    取最后一个记录,先倒序排列再取值 ,实例如下:
lists.Reverse();  // 先对集合进行倒序
var s2 = lists.FirstOrDefault();
MessageBox.Show(s2.ToString());

6.跳过前N条取余下的数据 Skip语句

//1 Linq语法:
var ss = (from r in db.Am_recProScheme
          orderby r.rpId descending
          select r).Skip(10); //跳过前10条数据,取10条之后的所有数据   
//2 Lamda语法:  
var ss1 = db.Am_recProScheme.OrderByDescending(p => p.rpId).Skip(10).ToList();
//3 Sql语法:
string sssql = "select * from  (select ROW_NUMBER()over(order by rpId desc) as rowNum, * from [Am_recProScheme]) as t where rowNum>10";

7.截取指定长度的数据 Take语句

//1 Linq语法:
var ss = (from r in db.Am_recProScheme
          orderby r.rpId descending
          select r).Take(10); // 截取前10条数据  
//2 Lamda语法:  
var ss1 = db.Am_recProScheme.OrderByDescending(p => p.rpId).Take(10).ToList();
//3 Sql语法:
string sssql = "select * from  (select ROW_NUMBER()over(order by rpId desc) as rowNum, * from [Am_recProScheme]) as t where rowNum<=10";

8.分页查询语句

【参考1:】
// Linq语法:
  var data=( from a in db.orderInfo select a ) .Skip((pageIndex-1) * pageSize).Take(pageSize).ToList();
// Lamda语法: pageIndex:当前页码,pageSize:分页数据显示条数
  var data=db.orderInfo.Skip((pageIndex-1)* pageSize).Take(pageSize).ToList();  
// sql语法:
  string sqlStr="SELECT TOP pageSize * FROM orderInfo WHERE orderId NOT IN(SELECT TOP( ( pageIndex - 1) * pageSize) orderId FROM orderInfo)";

【参考2:】
// 1 Linq语法:
var ss = (from r in db.Am_recProScheme
          where r.rpId > 10
          orderby r.rpId descending
          select r).Skip(10).Take(10); //取第11条到第20条数据
          
//2 Lamda语法:   Take(10): 数据从开始获取,获取指定数量(10)的连续数据
var ss1 = db.Am_recProScheme.OrderByDescending(p => p.rpId).Where(p => p.rpId > 10).Skip(10).Take(10).ToList();
//3 Sql语法:
string sssql = "select * from  (select ROW_NUMBER()over(order by rpId desc) as rowNum, * from [Am_recProScheme]) as t where rowNum>10 and rowNum<=20";

9.分组group by语句

【参考1:】
// Linq语法:
  var data= from a in db.orderInfo orderby a.orderId descending 
            group a by a.orderType into s select new{
            s.key,//分组字段
            s.sMoney=s.Sum(a=>a.orderMoney),//分组后算出总的消费额
            s.maMoney=s.Max(a=>a.orderMoney),//分组后算出最大的消费额
            s.miMoney=s.Min(a=>a.orderMoney)//分组后算出最小的消费额
            };
// Lamda语法:
  //使用GroupBy关键字进行分组查询(单个字段)
  var data=db.orderInfo.GroupBy(p => p.recType).Select(t=>t.Key).ToList();
  //使用GroupBy关键字进行分组查询(多个字段)
  var data=db.orderInfo.GroupBy(p =>new{ p.recType,p.orderId}).Select(t=>new{ recType=t.Key.recType,orderId=t.Key.orderId}).ToList();
  
// sql语法:
  string sqlStr="SELECT orderType , SUM(orderMoney), MAX(orderMoney), MIN(orderMoney) FROM orderInfo GROUP BY orderType";


【参考2】
//1 Linq语法:
var ss = from r in db.Am_recProScheme
         orderby r.rpId descending
         group r by r.recType into n
         select new
         {
             n.Key,  //这个Key是recType
             rpId = n.Sum(r => r.rpId), //组内rpId之和
             MaxRpId = n.Max(r => r.rpId),//组内最大rpId
             MinRpId = n.Min(r => r.rpId), //组内最小rpId
         };
foreach (var t in ss)
{
    Response.Write(t.Key + "--" + t.rpId + "--" + t.MaxRpId + "--" + t.MinRpId);
}
//2 Linq语法:
var ss1 = from r in db.Am_recProScheme
         orderby r.rpId descending
         group r by r.recType into n
         select n;
foreach (var t in ss1)
{
    Response.Write(t.Key + "--" + t.Min(p => p.rpId));
}
//3 Lamda语法:
var ss2 = db.Am_recProScheme.GroupBy(p => p.recType);
foreach (var t in ss2)
{
    Response.Write(t.Key + "--" + t.Min(p => p.rpId));
}
//4 sql语法:
string sssql = "select recType,min(rpId),max(rpId),sum(rpId) from Am_recProScheme group by recType";

10.包含Contains , 类似like ‘%%’语句

// Linq语法:  使用Contains关键字进行模糊匹配
  var data= from a in db.orderInfo where a.orderId.Contains(1) select a;
// Lamda语法:  使用Contains关键字进行模糊匹配
  var data=db.orderInfo.Where(t=>t.F_UserId.Contains("1")).ToList();
// sql语法:   使用like关键字进行模糊匹配
  string sqlStr="SELECT * FROM orderInfo WHERE orderId LIKE '%12%'";

11.Sql中的In – 通过Contains实现

【实例1:】
// Linq语法:
  var data= from a in db.orderInfo where (new int?[2213,43311,32422]).Contains(a.orderId) select a ; 
// Lamda语法:
  var data=db.orderInfo.Where(t=>(new int?[2213,43311,32422]).Contains(t.orderId)).ToList();
// sql语法:
  string sqlStr="SELECT * FROM orderInfo WHERE orderId IN (2213,43311,32422)";

【实例2:】
// Linq语法:
var ss = from p in db.Am_recProScheme
                  where (new int?[] { 24, 25,26 }).Contains(p.rpId)
                  select p;
foreach (var p in ss)
{
    Response.Write(p.Sorts);
}

// sql语法:
string st = "select * from Am_recProScheme where rpId in(24,25,26)";

12.内连接 Inner Join

【参考1】
// Linq语法:
var ss = from r in db.Am_recProScheme
         join w in db.Am_Test_Result on r.rpId equals w.rsId
         orderby r.rpId descending
         select r;
// Lamda语法:
var ss1 = db.Am_recProScheme.Join(db.Am_Test_Result, p => p.rpId, r => r.rsId, (p, r) => p).OrderByDescending(p => p.rpId).ToList();
// sql语法:
string sssql = "SELECT r.* FROM Am_recProScheme AS r INNER JOIN Am_Test_Result AS t ON r.[rpId] = t.[rsId] ORDER BY r.[rpId] DESC";


【参考2】
//Linq
var ss = from r in db.Am_recProScheme
         join w in db.Am_Test_Result on r.rpId equals w.rsId
         orderby r.rpId descending
         select r;
         
//Lambda
var ss1 = db.Am_recProScheme.Join(db.Am_Test_Result, p => p.rpId, r => r.rsId, (p, r) => p).OrderByDescending(p => p.rpId).ToList();

//SQL
string sssql = "select r.* from  [Am_recProScheme] as r inner join [dbo].[Am_Test_Result] as t on r.[rpId] = t.[rsId] order by r.[rpId] desc";

13.Linq和Lambda实现 左链接、右链接、内链接

  • 先准备个数据类

//客户类
public class Customer
    {
        public int id { get; set; }
        public string name { get; set; }
        public string email { get; set; }
    }
    //联系方式类
  public class CustomerContact
    {
        public int ccid { get; set; }
        public int CustomerId { get; set; }//客户的id,外键
        public string Phone { get; set; }
        public string Address { get; set; }
    }
   //模拟数据
           public static List<Customer> GetCustomer()//客户
        {
            List<Customer> list = new List<Customer>();
            list.Add(new Customer { id = 1, name = "刘德华",  email = "ldh@net.cn" });
            list.Add(new Customer { id = 2, name = "张学友",  email = "zxy@net.cn" });
            list.Add(new Customer { id = 3, name = "黎明",  email = "lm@net.cn" });
            list.Add(new Customer { id = 4, name = "郭富城",  email = "gfc@net.cn" });
            list.Add(new Customer { id = 4, name = "古天乐",  email = "gtl@net.cn" });
            return list;
        }
        public static List<CustomerContact> GetCustomerContact()//联系人
        {
            List<CustomerContact> list = new List<CustomerContact>();
            list.Add(new CustomerContact { ccid = 1,CustomerId=1, Phone="13566769988",Address="北京"});
            list.Add(new CustomerContact { ccid = 2, CustomerId = 1, Phone = "13566769986", Address = "天津" });
            list.Add(new CustomerContact { ccid = 3, CustomerId =2, Phone = "13677889900", Address = "香港" });
            list.Add(new CustomerContact { ccid = 4, CustomerId = 8, Phone = "13677889332", Address = "上海" });
            return list;
        }
  • Linq:左连接、右连接、内连接

// 1. 左连接
var LeftJoin = from cusetomer in GetCustomer()
                join cc in GetCustomerContact()
                on cusetomer.id equals cc.CustomerId 
                // 内连接的基础上, 加入这行代码就实现类左连接
                into JoinCC from cc in JoinCC.DefaultIfEmpty()
                select new
                {
                    CustomerName = cusetomer.name,
                    phone = cc != null ? cc.Phone : null
                };

// 2. 右连接:
//         右链接跟左链接相反,只需要改一下顺序就可以了。
 var LightJoin = from cc in GetCustomerContact()
                 join cusetomer in GetCustomer()
                 on cc.CustomerId equals cusetomer.id 
                 // 内连接的基础上, 加入这行代码就实现类左连接
                 into JoinCC from cusetomer in JoinCC.DefaultIfEmpty()
                select new
                {
                    phone = cc.Phone,
                    CustomerName =cusetomer != null ? cusetomer.name : null,
                };
// 3. 内连接:
var InnerJoin = from cc in GetCustomerContact()
                 join cusetomer in GetCustomer()
                 on cc.CustomerId equals cusetomer.id 
                 select new
                 {
                     phone = cc.Phone,
                     CustomerName = cusetomer.name
                 };
// 3-2 内连接:  多个条件关联的join 
var whiteDiagList = (from r1 in diagControlList
        where r1.ControlRelation == 1
        join r2 in args.DiagList on new { code = r1.DiagCode, name = r1.DiagName }
        equals new{code=r2.DiagCode,name=r2.DiagName}
        select r1).ToList<DiagControlModel>();
  • Lambda:左连接、右连接、内连接 [ 推荐使用上方Linq写法 ]

// 1. 左连接:
var LMLeftJoin = GetCustomer().GroupJoin(GetCustomerContact(), 
 a => a.id, b => b.CustomerId, (a, b) =>new { a,b })
     .SelectMany(c=>c.b.DefaultIfEmpty(), (c, b) 
     =>new { CustomerName= c.a.name,Phone=b!=null?b.Phone:null });

// 2. 右连接:参考左连接,两个集合对调位置就行了

// 3. 内连接:
var LMInnerJoin = GetCustomer().Join(GetCustomerContact(), a => a.id, b => b.CustomerId
, (a, b) => new { CustomerName = a.name, Phone = b.Phone });

14.交集、并集、差集

  • 简单类型的交集、并集、差集用法:

List<string> ListA = new List<string>();
List<string> ListB = new List<string>();
List<string> ListResult = new List<string>();

ListResult = ListA.Distinct().ToList();//去重
ListResult = ListA.Except(ListB).ToList();//差集
ListResult = ListA.Union(ListB).ToList();  //并集
ListResult = ListA.Intersect(ListB).ToList();//交集
  • 对象类型:List的交集、并集、差集用法

(1)先定义Model

    public class ItemModel
    {
        public string ItemCode { get; set; }
        public string ItemName { get; set; }
    }

(2)定义Model间如何比较

说明: 若不定义,比较的是两个引用

public class ItemModelComparer : IEqualityComparer<ItemModel>
{
        //比较
        public bool Equals(ItemModel x, ItemModel y)
        {
            bool checkFlag = true;

            if (Object.ReferenceEquals(x, y))
            {
                checkFlag = true;
            }
            else if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
            {
                checkFlag = false;
            }
            else
            {
                if (x.ItemCode == y.ItemCode) //若Model有多个条件则需要添加。例如 if(x.ItemCode==y.ItemCode && x.Other==y.Other)
                {
                    checkFlag = true;
                }
                else
                {
                    checkFlag = false;
                }
            }

            return checkFlag;

        }
    
        //实现获取哈希值
        public int GetHashCode(ItemModel model)
        {
            if (Object.ReferenceEquals(model, null)) return 0;
            int hashNurse = model.ItemCode.GetHashCode(); 

//            若两个集合有多个关联条件,则哈希值也需要进行计算
//            int hashOther=model.Other.GetHashCode();
//           int resultHash=hashNurse^hashOther;
//            return resultHash;

            return hashNurse;
        }
    
    }

(3)实例代码参考

List<ItemModel> ListA = new List<ItemModel>();
List<ItemModel> ListB = new List<ItemModel>();
List<ItemModel> ListResult = new List<ItemModel>();

ListResult = ListA.Distinct(new ItemModelComparer()).ToList();//去重
ListResult = ListA.Except(ListB, new ItemModelComparer()).ToList();//差集
ListResult = ListA.Union(ListB, new ItemModelComparer()).ToList();  //并集
ListResult = ListA.Intersect(ListB, new ItemModelComparer()).ToList();//交集

LINQ 提供了一种集成在 C# 中的查询语法,而 Lambda 表达式是一种用于创建匿名函数的简洁方式。它们都为开发者提供了更加简洁、灵活、高效的编程方式,使得处理数据和函数操作更为便捷和强大。