Linq知识集(翻译)

Linq简介

简介

LINQ(Language Integrated Query),语言集成查询。.NET Framework3.5C# 3.0一同发行。提供通用查询语法来支持不同数据源,包括内存中的数据、数据库数据(SQL、Oracle等)、XML文档等。如果没有Linq,那么你需要根据数据源的不同,学习不同的查询语法来支持数据查询工作。

Linq提供者(Provider),通过实现IQueryProviderIQueryable接口来实现具体数据源数。而程序开发人员只需要针对Linq提供者提供的数据查询即可,可以像使用对象一样来编写查询。

架构
Linq架构

例子

Linq语句包含三个部分:

  • 初始化: 确定数据源
  • 条件:where、过滤、排序等
  • 查询:单独查询、分组查询、联合查询等

Linq语句的三种书写方式:
查询语句式
方法式
混合式

//准备数据源(内存中的数组)
List<int> intList = new List<int>()
{1,2,3,4,5,6,7,8,9};
//使用查询语句式的Linq
var linqQueries = (from obj in intList
                    where obj > 5
                    select obj);
//执行
foreach(var item in linqQueries)
{
    Console.Write(item + " ");
}

优点:

  1. 无需为新的数据源学习新的查询语言语法
  2. 精简代码
  3. 编译时检查错误
  4. 提供内置方法方便实现查询、排序、分组等
  5. 查询是可复用的

缺点

  1. 很难写出复杂的SQL查询
  2. 并没有实现所有的SQL功能,比如存储过程
  3. 如果查询编写的不好,性能将会非常差
  4. 修改查询,则需要重新部署应用,重新发布dll至服务器

IEnumerableIQueryable

IEnumerableSystem.Collection命名空间下,它只有一个GetEnumerator()方法。该方法的定义如下(泛型和非泛型)

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}
public interface IEnumerable<out T>:IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

IQueryableSystem.Linq命名空间下,继承自IEnumerable接口。因此我们可以把一个IQueryable的变量赋值给IEnumerable变量。IQueryable接口有一个IQueryProvider成员。

namespace System.Linq
{
    public interface IQueryable: IEnumerable
    {
        Expression Expression {get;}
        Type ElementType {get;}
        IQueryProvider Provider {get;}
    }
    public interface IQueryProvider
    {
        IQueryable CreateQuery(Expression expression);
        IQueryable<TElement> CreateQuery<TElement>(Expression expression);
        object Execution(Expression expression);
        TResult Execute<TResult>(Expression expression);
    }
}

IEnumerable与IQueryable区别
总结:

IEnumerable
  1. System.Collections命名空间下的接口
  2. 从数据库查询数据时,IEnumerable在服务端(数据库)执行select语句。数据加载到客户端内存中,然后在内存中的数据获取需要的(执行条件过滤)
  3. 当需要查询内存中数据(List、Array等)要使用IEnumerable接口
  4. 大多用于LINQ to Object 和LINQ to XML查询
  5. IEnumerable只能向前获取数据,不能往后或截取片段
  6. 不支持自定义查询
  7. 不支持懒加载,因此不支持分页场景Enumerable类中也有Where方法的实现,该方法定义如下:
IEnumerable<TSource> Where<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate);
IQueryable
  1. System.Linq命名空间下的接口
  2. 从数据库查询数据时,IQueryable在服务端(数据库)执行带过滤的查询语句,然后获取需要的数据
  3. 当需要获取非内存数据(如远端数据库)时使用IQueryable接口
  4. 通常用于LINQ to SQL 和LINQ to Entities查询
  5. 类型集合只能往前,不能往后或截取片段
  6. 支持延迟执行
  7. 通过CreateQueryExecutes方法来支持自定义查询
  8. 支持延迟加载(lazy loading),支持分页场景

查看Where方法定义如下,它是IQueryable接口的一个扩展方法

public static IQeueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Func<TSource, bool> predicate)

关于何时使用IEnumerableIQueryable,在之前的文章中也有过讨论

扩展方法

C#的扩展方法允许我们在不改变已有类的代码前提下,向该类添加一个方法。具体做法是定义一个静态类,该静态类下实现一个静态方法,方法的第一个参数需要使用this关键字。举个例子,在string类中添加一个方法获取字符串中单词的个数。

public static class StringExtension
{
    public int GetWordCount(this string str)
    {
        if(!IsEmptyOrNull(str))
        {
            return str.Split(' ').Length;
        }
        return 0;
    }
}

此时,我们在项目中定义的string类型变量,就可以直接以.GetWordCount()方式调用这个方法了。因为这是一个静态方法,当然也可以使用类名.方法名()的方式来调用。

//扩展方法调用
string str = "hello word";
Console.WriteLine(str.GetWordCount());
//静态方法调用
Console.WriteLine(StringExtesion.GetWordCount(str));

Enumerable.cs类中实现了许多扩展方法如Where、Join等,既然是扩展方法,调用方式就有上面两种。

List<int> intList = new List<int>()
{1,2,3,4,5,6,7,8,9};
var linqList = (from obj in intList
                select obj)
                .Where(obj => obj > 5);
IEnumerable<int> enurableList = Enumerable.Where(intList, obj => obj > 5);
foreach(int item in enurableList)
{
    Console.Write(item + "  ");
}

LINQ查询操作符

LINQ操作符就是一系列的扩展方法用来写LINQ查询。

查询操作符(Select):用来投影(Projection),从数据源中查询数据的机制。你可以按数据源原样查询出,也可以创建自己的结果形式。下面举例仅引用一些原文的图片,代码片段有点长,后期考虑github上建个repo。

下面两条语句将Employee对象原样查询出来

下面两条语句构建一个新的数据对象,不包含ID字段

下面两条语句构建一个新的数据匿名对象

下面两条语句创建带有索引值的对象

SelectMany查询,用于合并一系列的结果成为一个。下面看个例子:

  static void Main(string[] args)
        {
            List<string> nameList =new List<string>(){"Pranaya", "Kumar" };
            IEnumerable<char> methodSyntax = nameList.SelectMany(x => x);
            foreach(char c in methodSyntax)
            {
                Console.Write(c + " ");
            }
            //相当于下面的查询方式
            IEnumerable<char> querySyntax = from str in nameList
                                            from ch in str
                                            select ch;
            foreach(char c in querySyntax)
            {
                Console.Write(c + " ");
            }
            Console.ReadKey();
        }

另外一个例子

var methodSyntax = Student.GetStudents()
                    .SelectMany(stu => stu.Programming, 
                    (stu, pro) => new
                    {
                        StudentName = stu.Name,
                        Programing = pro
                    })
                    .Distinct()
                    .ToList();
var querySyntax = (from stu in Student.GetStudents()
                    from pro in stu.Programming
                    select new 
                    {
                        StudentName =stu.Name,
                        Programing = pro
                    }).Distinct()
                    .ToList();
//foreach(var item in methodSyntax)
foreach(var item in querySyntax)
{
    Console.WriteLine(item.StudentName + " => " + item.Programing);
}
public class Student
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public List<string> Programming { get; set; }
    public static List<Student> GetStudents()
    {
        return new List<Student>()
        {
            new Student(){ID = 1, Name = "James", Email = "James@j.com", Programming = new List<string>() { "C#", "Jave", "C++"} },
            new Student(){ID = 2, Name = "Sam", Email = "Sara@j.com", Programming = new List<string>() { "WCF", "SQL Server", "C#" }},
            new Student(){ID = 3, Name = "Patrik", Email = "Patrik@j.com", Programming = new List<string>() { "MVC", "Jave", "LINQ"} },
            new Student(){ID = 4, Name = "Sara", Email = "Sara@j.com", Programming = new List<string>() { "ADO.NET", "C#", "LINQ" } }
        };
    }
}

Linq知识集二

说明:翻译接上篇,上一篇翻译到了LINQ中的操作符,所谓操作符就是LINQ中的一系列扩展方法,用来对数据操作的一系列方法(如查询、过滤、排序等)。我们已经介绍了查询的Select方法,接下来继续。

LINQ中过滤操作符

过滤操作符,顾名思义就是对数据进行符合条件过滤。LINQ中过滤方法有两个:

  • Where
  • OfType

Where过滤

where通常需要至少一个条件,通过谓词来具体化条件。条件可以是一下操作符

==, >=, <=, &&, ||, >, <, etc.

Where方法的签名可以看出,它是作为IEnumerable<T>的扩展方法来实现的。

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);
断言

断言是用来测试每个数据是否符合条件的方法,看下面例子

 List<int> intList = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            //Method Syntax
            IEnumerable<int> filteredData = intList.Where(num => num > 5);
            //Query Syntax
            IEnumerable<int> filteredResult = from num in intList
                                              where num > 5
                                              select num;
            
            foreach (int number in filteredData)
            {
                Console.WriteLine(number);
            }
            Console.ReadKey();

num => num > 5 这个匿名函数就是一个断言,Where对数据源中的每一项都执行该方法。

where第一个重载方法断言参数Func<int, bool> preidcate期望是一个整型的输入参数,返回一个布尔类型值。这是一个泛型代理,需要一个或多个输入参数,以及一个输出参数。最后一个参数作为输出参数,且是强制必须要的,输出参数可选。上面的lambada表达式即为传给Func的参数,可以重写为如下形式。

Func<int, bool> preidicate = i => i > 5;
//当然也可以把这个匿名方法写成一个有名字的方法
//public static bool CheckNumber(int number)
//{
//    if(number > 5)
//        return true;
//    else
//        return false;
//}
IEnumerable<int> filterData = intList.Where(predicate);
//IEnumerable<int> filterData = intList.Where(num => CheckNumber(num));

where第二个重载方法,断言方法的int型参数表示数据源元素的索引位置。参考下面例子

List<int> intList = new List<int>{1,2,3,4,5,6,7,8,9};
var oddNumberWithIndex = intList.Select((num, index) => 
                    new {
                        Numbers = num,
                        IndexPosition = index
                    }).Where(x => x.Numbers % 2 != 0)
                    .Select(data => 
                    new {
                        Number = data.Numbers,
                        IndexPostion = data.IndexPostion
                    });
foreach(var item in oddNumberWithIndex)
{
      Console.WriteLine($"IndexPosition :{item.IndexPosition} , Value : {item.Number}");
}

OfType操作符

OfType用于过滤数据源中指定类型的数据,实际上也可以通过where来判断数据源中数据的类型,来获得指定数据。看下面的例子

List<object> datasource = new List<object>()
{"Tom", "Mary", 1, 2, "Price", 40, 20, 10};
//通过OfType的泛型方法可以直接获取到整型数组
List<int> intData = datasource.OfType<int>().ToList();
//采用查询语法where条件来获取指定类型数据
var strData = from name in datasource
                where name is string
                select name;

Set操作符

set操作符用于根据数据源元素的显隐来产生结果集,意味着这些操作有可能针对单个数据源或多个数据源,输出数据中这些数据有可能出现有可能不出现。主要有以下方法:

  • Distinct : 用于查询无重复数据的方法
  • Except: 用于查询在某一集合里的且不在另一集合内的数据(1.Except(2)在1不在2, 2.Except(1)在2不在1)
  • Intersect : 用于查询多个数据源相交的集合数据(在1且在2)
  • Union :用于查询具有相同结构的数据源数据的集合(1和2一起)

4个操作结果如下图
在这里插入图片描述

LINQ Distinct

Distinct的两个重载签名如下:

public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source);
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer);

举两个例子说明Distinct的用法

 List<int> intCollection = new List<int>(){1,2,3,2,3,4,4,5,6,3,4,5};
 //查询数组中去除重复项后的数据
var MS = intCollection.Distinct();
foreach (var item in MS)
{
    Console.WriteLine(item);
}

如果数据源是复杂类型的列表,当使用Distinct的默认Comparer时,只会判断两个引用对象是否相同,而不会判断对象中每个属性值是否相等。

List<Student> students = new List<Student>()
            {
                new Student {ID = 101, Name = "Preety" },
                new Student {ID = 102, Name = "Sambit" },
                new Student {ID = 103, Name = "Hina"},
                new Student {ID = 104, Name = "Anurag"},
                new Student {ID = 102, Name = "Sambit"},
                new Student {ID = 103, Name = "Hina"},
                new Student {ID = 101, Name = "Preety" },
            };
//Using Method Syntax
var MS = students
        .Distinct().ToList();
// var MS = students
//         .Select(stu => new {
//             ID = stu.ID,
//             Name = stu.Name
//         })
//         .Distinct().ToList();
foreach (var item in QS)
{
    Console.WriteLine($"ID : {item.ID} , Name : {item.Name} ");
}

运行上面代码,获得输出全部学生ID、姓名,原因上面已经解释。如何解决这个问题,可以通过以下三种方法来实现

  1. 定义一个类,实现IEqualityComparer接口,然后将这个类对象传递给Distinct作为参数
  2. 重写Student类的EqualsGetHashCode方法
  3. 使用匿名类,如上面示例代码中Select中创建匿名类
  4. Student类中实现IEquatable<T>接口
//方法1
public class StudentComparer : IEqualityComparer<Student>
{
    public bool Equals(Student x, Student y)
    {
        if(object.ReferenceEquals(x, y))
            return true;
        if (object.ReferenceEquals(x,null) || object.ReferenceEquals(y, null))
            return false;
        return x.ID == y.ID && x.Name == y.Name;
    }
    public int GetHashCode(Student obj)
    {
        if (obj == null)
            return 0;
        int IDHashCode = obj.ID.GetHashCode();
        int NameHashCode = obj.Name == null ? 0 : obj.Name.GetHashCode();
        return IDHashCode ^ NameHashCode;
    }
}
//方法2,Student类中重写下面两个方法
public override bool Equals(object obj)
{
    return this.ID == ((Student)obj).ID && this.Name == ((Student)obj).Name;
}
public override int GetHashCode()
{
    int IDHashCode = this.ID.GetHashCode();
    int NameHashCode = this.Name == null ? 0 : this.Name.GetHashCode();
    return IDHashCode ^ NameHashCode;
}
//方法4,Student类继承IEquatable<Student>
public bool Equals(Student other)
{
    if (object.ReferenceEquals(other, null))
    {
        return false;
    }
    if (object.ReferenceEquals(this, other))
    {
        return true;
    }
    return this.ID.Equals(other.ID) && this.Name.Equals(other.Name);
}
IEqualityComparer<T>IEquatable<T>区别

从上面的方法1和方法4势力代码能看出两者区别。
IEqualityComparer<T>接口,是使用第三方类,来实现两个泛型类<T>对象之间的比较。
IEquatable<T>接口,则是使用泛型类<T>与该类自身的新对象进行比较。

LINQ中的Except

首先来看Except的两个方法签名,和Distinct基本一样。只是该操作是针对两个数据源,因此多一个数据参数。同样,第二个方法的签名最后一个参数传入一个IEqualityComparer接口,意味着处理复杂类型数据时,需要自己实现这个接口进行对象间的比较。

public static IEnumerable<TSource> Except<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second);
public static IEnumerable<TSource> Except<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);

接下来通过例子说明

List<int> dataSource1 = new List<int>() { 1, 2, 3, 4, 5, 6 };
List<int> dataSource2 = new List<int>() { 1, 3, 5, 8, 9, 10 };
//Method Syntax
var MS = dataSource1.Except(dataSource2).ToList();
var QS = (from num in dataSource1
        select num)
        .Except(dataSource2).ToList();
foreach (var item in QS)
{
    Console.WriteLine(item);
}

下面的代码处理复杂类,如果Student类没有重写Equals方法和GetHasCode方法的话,输出结果将不是我们期望的。参考Distinct方法中给Student重写方法、实现IEqualityCompare接口、匿名类等方法来实现我们期望的结果。

  List<Student> AllStudents = new List<Student>()
    {
        new Student {ID = 101, Name = "Preety" },
        new Student {ID = 102, Name = "Sambit" },
        new Student {ID = 103, Name = "Hina"},
        new Student {ID = 104, Name = "Anurag"},
        new Student {ID = 105, Name = "Pranaya"},
        new Student {ID = 106, Name = "Santosh"},
    };
    List<Student> Class6Students = new List<Student>()
    {
        new Student {ID = 102, Name = "Sambit" },
        new Student {ID = 104, Name = "Anurag"},
        new Student {ID = 105, Name = "Pranaya"},
    };
    
    //Method Syntax
    var MS = AllStudents.Except(Class6Students).ToList();
    //Query Syntax
    var QS = (from std in AllStudents
                select std).Except(Class6Students).ToList();
    
    foreach (var student in MS)
    {
        Console.WriteLine($" ID : {student.ID} Name : {student.Name}");
    }

LINQ中的Intersect

首先来看Intersect的两个方法签名,和上面的Except基本一样。所以后面的示例代码以及如何处理复杂类数据元素都不具体提供示例了。

public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second);
public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);

LINQ中的Union

首先来看Union的两个方法签名,和上面的Except基本一样。所以后面的示例代码以及如何处理复杂类数据元素都不具体提供示例了。

public static IEnumerable<TSource> Union<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second);
public static IEnumerable<TSource> Union<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值