Linq简介
简介
LINQ(Language Integrated Query
),语言集成查询。.NET Framework3.5
和C# 3.0
一同发行。提供通用查询语法来支持不同数据源,包括内存中的数据、数据库数据(SQL、Oracle
等)、XML文档等。如果没有Linq
,那么你需要根据数据源的不同,学习不同的查询语法来支持数据查询工作。
Linq提供者(Provider
),通过实现IQueryProvider
和IQueryable
接口来实现具体数据源数。而程序开发人员只需要针对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 + " ");
}
优点:
- 无需为新的数据源学习新的查询语言语法
- 精简代码
- 编译时检查错误
- 提供内置方法方便实现查询、排序、分组等
- 查询是可复用的
缺点
- 很难写出复杂的SQL查询
- 并没有实现所有的SQL功能,比如存储过程
- 如果查询编写的不好,性能将会非常差
- 修改查询,则需要重新部署应用,重新发布dll至服务器
IEnumerable
与IQueryable
IEnumerable
在System.Collection
命名空间下,它只有一个GetEnumerator()
方法。该方法的定义如下(泛型和非泛型)
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerable<out T>:IEnumerable
{
IEnumerator<T> GetEnumerator();
}
IQueryable
在System.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
- 在
System.Collections
命名空间下的接口 - 从数据库查询数据时,
IEnumerable
在服务端(数据库)执行select语句。数据加载到客户端内存中,然后在内存中的数据获取需要的(执行条件过滤) - 当需要查询内存中数据(List、Array等)要使用
IEnumerable
接口 - 大多用于LINQ to Object 和LINQ to XML查询
IEnumerable
只能向前获取数据,不能往后或截取片段- 不支持自定义查询
- 不支持懒加载,因此不支持分页场景
Enumerable
类中也有Where
方法的实现,该方法定义如下:
IEnumerable<TSource> Where<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate);
IQueryable
- 在
System.Linq
命名空间下的接口 - 从数据库查询数据时,
IQueryable
在服务端(数据库)执行带过滤的查询语句,然后获取需要的数据 - 当需要获取非内存数据(如远端数据库)时使用
IQueryable
接口 - 通常用于LINQ to SQL 和LINQ to Entities查询
- 类型集合只能往前,不能往后或截取片段
- 支持延迟执行
- 通过
CreateQuery
和Executes
方法来支持自定义查询 - 支持延迟加载(
lazy loading
),支持分页场景
查看Where
方法定义如下,它是IQueryable
接口的一个扩展方法
public static IQeueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Func<TSource, bool> predicate)
关于何时使用IEnumerable
和IQueryable
,在之前的文章中也有过讨论
扩展方法
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、姓名,原因上面已经解释。如何解决这个问题,可以通过以下三种方法来实现
- 定义一个类,实现
IEqualityComparer
接口,然后将这个类对象传递给Distinct
作为参数 - 重写
Student
类的Equals
和GetHashCode
方法 - 使用匿名类,如上面示例代码中
Select
中创建匿名类 - 在
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);