C#中的LINQ
Posted Butterapple
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#中的LINQ相关的知识,希望对你有一定的参考价值。
从自己的印象笔记里面整理出来,排版欠佳。见谅!
1、LINQ: 语言集成查询(Language Integrated Query)
实例:
var q=
from c in categories
join p in products on c equals p.Category into ps
select new{Category=c, Products=ps};
2、LINQ 类型
- LINQ to Objects(或称LINQ to Collection),这是LINQ 的最基本功能,针对集合对象进行查询处理,包括基本的汇总与过滤都在这个功能内实现。
- LINQ to SQL,这是LINQ 功能的SQL Server 数据库版本,通过LINQ 指令,可以直接查询SQL Server 数据库,而完全无须编写SQL 指令。
- LINQ to XML,是针对XML 设计的LINQ 功能,它通过XDocument 与XElement 两个主要类的功能,进行LINQ 语法解析与XML 内的元素的查询操作。可用来替代现有以XPath方式解析XML文件的功能。
- LINQ to DataSet(或称LINQ to ADO.NET),是为现有以DataSet或DataTable 对象开发应用程序提供支持LINQ操作的功能,以AsEnumerate() 为基础,将DataSet与DataTable内的数据转换成IEnumerable接口的操作集合,即可直接使用LINQ to Objects 的方式查询。
3、LINQ 的基础
LINQ本身以IEnumerable<T> 两个接口为基础,IEnumerable<T> 则负责泛型的集合,而目前.NET Framework 内的泛型集合类(System.Collection.Generic命名空间)都已实现IEnumerable<T> ,所以基本上在.NET Framework 内所有的集合对象都能使用LINQ 查询方法进行处理,但除了IEnumerable<T> 接口外,在LINQ 里面还包含了数个语言增强的功能。
LINQ 本身的基础建设实现与System.Linq 命名空间内,若没有使用using 引入这个命名空间的话,所有LINQ 功能都无法使用。
3.1、扩展方法
在System.Linq 命名空间内有一个叫做Enumerable 的静态类,这个类实现了许多的方法,而且这些方法都可以在任何实现IEnumerable<T> 的集合对象中找到,这个技术称为扩展方法(extension method),扩展方法赋予了程序设计语言可在现有类下扩展类的功能,而且不需要修改原本的类的程序代码。
要实现扩展方法十分简单,首先决定要为哪个类实现扩展方法,接着建立一个静态类(static class),名称建议使用“要扩展的类名称”+Extension 字样,例如像扩展Int 方法,就将类命名为Int32Extension;要扩展DateTime,就命名为DateTimeExtension。接下来在类内加入要扩展的方法,但要注意两件事:
(1)必须是静态方法,且名称不可以和现有的方法冲突。
(2)至少要有一个扩展类型输入参数,扩展类型输入参数格式必须是“this[要扩展的类名称][参数名称]”,若有两个以上的参数,则扩展类型输入参数必须放在第一个,且不能设置默认值。
下面是一个例子,想要扩展int 和double 两个对象,分为赋予产生货币字符串与百分比字符串的功能,按照扩展方法的规则,先定义Int32Extension 与DoubleExtension 两个类,并声明为static class
public static class Int32Extension
{
public static string FormatForMoney(this int Value)
{
return Value.ToString("$###,###,###,##0");
}
}
public static class DoubleExtension
{
public static string FormatPercent(this doubel Value)
{
return Value.ToString("0.00%");
}
}
using System;
namespace ExtensionTest
{
class Program
{
static void Main(string[] args)
{
int money = 12456789;
double p = 0.123456;
Console.WriteLine("{0}",money.FormatForMoney());
Console.WriteLine("{0}", p.FormatPercent());
}
}
}
若要使用不同命名空间内的扩展方法,记得要加上命名空间声明(使用using),否则编译器会找不到扩展方法的类。
3.2、匿名类型与对象初始化器
在开头的实例中的完整例子是这样的:
var studentScoreQuery=
from student in students
join csScore in csScores on student.ID equals csScore.ID
join dbScore in dbScores on student.ID equals dbScore.ID
select new
{
ID=student.ID,
Name=student.Name,
ScoreSum=csScore.Score+dbScore.Score,
ScoreAvg=(csScore.Score+dbScore,Score)/2
};
语法中有一个select new ,可以按做设置的属性自动产生类对象,并且自动赋予数值,这个语法包含了两个语言功能:对象初始化器与匿名类型。
对象初始化器(object initializer)允许在程序中通过声明的方式直接给对象属性进行数值的初始化,而不必刻意有参数的构造函数,可降低程序员维护多个构造函数的负担,
private static List<Student> GetStudents()
{
return new List<Student>(new []{
new Student(){
ID="001",Name="张三"
},
new Student(){
ID="002",Name="李四"
}
});
}
Person jeff=new Person("Jeff"){Age="20"}
List<Cat> cats=new List<Cat>
{
new Cat(){Name="1",Age=1},
new Cat(){Name="2",Age=2}
};
Dictionary<int,Student> students=new Dictionary<int,Student>()
{
{111,new Student{FirstName="1",LastName="2"}},
{112,new Student{FirstName="2",LastName="3"}}
}
声明匿名类型数组
var o=new
{
name="123",
value="456"
};
var o1=new []{
new {name="123",value="456"},
new {name="222",value="ddd"}
};
匿名类型的限制:
- 匿名类型一般只会用在用一个函数内,如果要让它被其他函数共享,则必须要动用到Reflection ,或是利用 .NET4.0 提供的动态类型(dynamic types)机制。
- 匿名类型只能有属性,不可以有方法、事件或字段等。
- 两个匿名类型对象的相等(Equal),必须要另个对象的属性值都相等才行。
- 匿名类型的初始化只能利用对象初始化器来进行,其属性在生成后会变成只读。
Var 类型的限制:
1、使用var类型时复制语句的右边不可以是null,否则编译器无法推断出其类型。
2、var 类型只能用于局部变量的声明,不能用于全局变量、类层级的变量或是函数的返回值。
3、var 类型不可用在匿名委派或是方法群组中。
3.3、yield 指令与延迟查询
yield 指令,它可以让程序员以只传回每个元素的方式来自动生成 IEnumerable<T> 对象。如下两个例子所示:
private static IEnumerable<int> GetCollection1()
{
List<int> list=new List<int>();
for(int i=1;i<=5;i++)
{
list.Add(i);
}
return list;
}
private static IEnumerable<int> GetCollection2()
{
for(int i=1;i<=5;i++)
{
yield return i;
}
}
private static IEnumerable<int> GetCollection3(IEnumerable<int> NumberSeries)
{
foreach(var number in NumberSeries)
{
if(number>100)
yield break;
else
yield return number;
}
}
yield 指令的用途其实并不单单只是精简写法, yield 指令也是一种编译器魔法,会在程序编译的同时,使用yield 指令的程序段生成迭代运算的有限状态机(state machine)。这个状态机监控对象在“巡航”时所作的访问动作,在每一次“巡航”调用触发时才会真正进入集合获取数据,这个特性在LINQ 中相当重要,因为LINQ 不只是想支持集合对象,还要支持外部数据源。若没有这个特性,当LINQ 对外部数据源进行查询时,要一次取回所有数据,而无法只针对程序的需要来获取数据,这样不但会造成时间上的浪费(抓取所有数据所需的网络传输时间),也会造成空间上的浪费。所以LINQ 在设计时使用这个机制,让对集合对象的访问推迟到真正查询时才触发,这个机制称为延迟查询(Deferred Query)或延迟执行(Deferred Execution)。
3.4、Fluent Interface
Enumerbale 内所包含的LINQ 扩展方法,都返回IEnumerable<T>,这代表可以使用直接串接的方式来调用多个LINQ 函数,而不用为每个函数调用都编写一行程序,这个技术称为Fluent Interface:Fluent Interface 具有三项特性:
- 通过调用方法来定义对象内容。
- 对象会自我引用(self-referential),且新的对象内容会和最后一个对象内容等价。
- 通过返回void 内容(就是null 或不返回最后的对象内容)或非Fluent Interface 的对象结束。
可以直接对集合进行两次过滤:
var query=list.Where(c=>c<10000).Where(c=>c>1000);
或是提取需要的数据结构
var query=list.Where(c=>c<10000).Select(c=>new {id=c});
4、LINQ 语句
实例:查询单价1000的商品:
var query=from product in db.GetProducts()
where product.Price>1000
select product;
实例:查询商品目前的销售数字:
var query=from o in db.GetOrderDetails()
group by o.ProductID into g
select new{
ProductID=g.Key,
Qty=g.Sum(p=>p.ProductID)
};
实例:查询两个集合内都有数字的LINQ 语句:
List<int> list1=new List<int>(){1,2,3,4,5,6};
List<int> list2=new List<int>(){6,4,2,7,9,0};
var query=from item1 in list1
join item2 in list2 on item1 equals item2
select item2;
foreach(var q in query)
Console.Write("{0}",q);
1、LINQ 函数
1.1、查询结果过滤 :where()
Enumerable.Where() 是LINQ 中使用最多的函数,大多数都要针对集合对象进行过滤,因此Where()在LINQ 的操作上处处可见,Where()的主要任务是负责过滤集合中的数据:其原型如下:
public static IEnumerbale<TSouce> 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);
Where() 的参数是用来过滤元素的条件,它要求条件必须传回bool,以确定此元素是否符合条件,或是由特定的元素开始算起(使用Func<TSource,int bool>,中间的传入参数代表该元素在集合中的索引值),例如要在一个数列集合中找出大于5的数字时:
List<int> list1=new List<int>(){6,4,2,7,9,0};
list1.Where(c=>c>5);
或者
list1.Where(c=>c>=1).Where(c=>c<=5);
list1.Where(c=>c>=1&&c<=5);
Where() 的判断标准是,只要判断函数返回true 就成立,反之则取消。
1.2、选取数据: Select()、SelectMany()
通常在编写LINQ 函数调用时较少用到选取数据的函数(因为函数调用会直接返回IEnumerable<T> 集合对象 ),但在编写LINQ语句时十分常用,在语句中若编写了select new指令,它会被编译器转换成LINQ 的Select(),Select() 的原型如下:
public static IEnumerable<TResult> Select<TSource,TResult>(this IEnumerable<TSource> source,Func<TSource,TResult> selector);
public static IEnumerable<TResult> Select<TSource,TResult>(this IEnumerable<TSource> source,Func<TSource,int,TResult> selector);
与Where() 类似,Select() 也可以按照元素所在的位置判断处理,而Select()所指定的处理式selector 必须传回一个对象,这个对象可以是现有的类型,也可以是匿名的类型,既可以通过Select() 来重新组装所需数据。例:
var query=db.OrderDetails.Where(o=>o.ID==12345).Select(o=>new{ ProductID=o.ProductID,Qty=o.Qty});
Select() 的另一个相似函数SelectMay() 则是处理有两个集合对象来源的数据选取,其原型如下:
public static IEnumerable<TResult> SelectMany<TSource,TResult>(this IEnumerable<TSource> source,Func<TSource,IEnumberable<TResult>> selector);
public static IEnumerable<TResult> SelectMany<TSource,TResult>(this IEnumerable<TSource> source,Func<TSource,int,IEnumberable<TResult>> selector);
public static IEnumerable<TResult> SelectMany<TSource,TCollection,TResult>(this IEnumerable<TSource> source,Func<TSource,IEnumberable<TCollection>> collectionSelector,Func<TSource,TCollection,TResult> resultSelector);
public static IEnumerable<TResult> SelectMany<TSource,TCollection,TResult>(this IEnumerable<TSource> source,Func<TSource,int,IEnumberable<TCollection>> collectionSelector,Func<TSource,TCollection,TResult> resultSelector);
SelectMany() 在LINQ 函数调用上较难理解,但如果把它想象成数据库的CROSS JOIN ,相对来说就容易懂了,例:
List<int> list1=new List<int>(){1,2,3,4,5,6};
List<int> list2=new List<int>(){6,4,2,7,9,0};
var query=list1.SelectMany(o=>list2);
foreach(var q in query)
Console.WriteLine("{0}",q);
输出结果:
6424790642790642790642790642790642790
因为“642790”输出了 6次,list1 内的元素是6个,所以可以知道SelectMany() 会按照list1 内的元素个数调用它的selector,并组装集合输出。
1.3、群组数据:GroupBy()、ToLookup()
汇总数据是查询机制的基本功能,而在汇总之前,必须要先将数据做群组化,才能进行统计,LINQ 的群组数据功能由Enumerable.GroupBy()函数提供。
GroupBy() 会按照给定的key(keySelector)以及内容(elementSelector),产生群组后的结果(IGroup 接口对象或是由resultSelector生成的结果对象),例:
List<int> sequence =new List<int>(){1,2,3,4,3,2,4,6,4,2,4};
var group=sequence.GroupBy(o=>o);
foreach(var g in group)
{
Console.WrilteLine("{0} count:{1}",g.Key,g.Count());//计算每个数出现的次数。
GroupBy 设置了使用数列本身值作为Key值,并且利用这个Key 分组产生分组的数据(IGrouping<TKey,TElement类型),再对分组的数据进行汇总。结果如下:
1 count: 1
2 count: 3
3 count: 2
4 count: 4
6 count: 1
若是想要在返回之前对分组后的元素做处理,可以传入elementSelector 而若是要在元素处理后产生结果的话,则可以传入resultSelector,这样返回的集合会是以resultSelector 返回的类型为主,而不是默认的IGroup接口。
除了GroupBy()能群组化数据外、另外一个具有群组化数据能力的是ToLookUp(),它可以生成具有群组化特性的集合对象,由ILookup<TKey,TElement>组成。
ToLookup()看起来和GroupBy()有些类似,但是它会另外生成一个新的集合对象,这个集合对象由ILookup<TKey,TElement>所组成,允许多个键值存在,且一个键值可包含许多关联的实值。例:
var nameValuesGroup=new[]
{
new{name="Allen", value=65,group="A"},
new{name="Abbey",value=120,group="B"},
new{name="Sue",Value=200,group="A"}
};
var lookupValues=namValuesGroup.ToLookup(c=>c.group);
foreach(var g in lookupValues)
{
Console.WriteLine("===Group: {0}===",g.Key);
foreach(var item in g)
{
Console.WriteLine("name:{0},value:{1}",item.name,item.value);
}
}
GroupBy()本身具有延迟执行的特性,而ToLookup()没有。
1.4、联接数据: Join() 与GroupJoin()
身为一个查询机制,将两个集合进行联接(join)也是理所当然的,尤其是在进行数据的对比和汇总时,联接机制显得更重要。在LINQ 函数中,有Enumerable.Join() 函数负责处理联接,其原型如下:
public static IEnumerable<TResult> Join<TOuter,TInner,TKey,TResult>(this IEnumerable<TOuter> outer,IEnumerable<TInner> inner,Func<TOutput,TKey> outerKeySelector,Func<TInner,TKey> innerKEySelector,Func<TOuter,TInner,TResult> resultSelector)
....
由原型可看到它将原本的集合视为TOuter,而将传入的集合视为TInner,儿还要决定由哪个属性或成员当Key,最后由resultSelector 来输出联接的结果。例:
var query=from item1 in list1
join item2 in list2 on item1 equals item2
select item2;
var query3=list1.Join(
list2,
item1=>item1,
item2=>item2,
(item1,item2)=>item2
);
Enumerable<T>.Join()使用的是INNER JOIN 的概念,当TInner.Key 和TOuter.Key相同时,才会将元素输出到resultSelector 作为参数。
目前常用的联接模式,INNER JOIN由 Enumerable<T>.Join() 实现,CROSS JOIN 由Enumerable<T>.SelectMany() 实现,还有一种JOIN 模式没有考虑,LEFT OUTER JOIN模式,要实现这个模式,必须要借助GroupJoin()方法来实现。
GroupJoin 和 Join() 十分相似,不过它却又Join() 和 GroupBy() 两者的功能,在Join() 的情况下,它会留下TInner 和TOuter 两边都有的值,但在GroupJoin() ,它会将TOuter 的值作为Key,并依此来对TInner 做群组化后输出,例:
var query4=from item1 in list1
join item2 in list2 on item1 equals item2 into g
from item in g.DefaultIfEmpty()
select new{ v=item1,c=item};
var query5=list1.GroupJoin(
list2,
item1=>item1,
item2=>item2,
(item1,item2)=>new {v=item1,c=item2.Count()});
1.5、数据排序:OrderBy() 与ThenBy()
数据排序是在数据处理中常见的功能,在LINQ 内的排序主要是以OrderBy 函数为主,而为了支持连续条件的排序,可加上ThenBy 函数,以便处理多重条件排序的需求。基于LINQ的延迟查询机制,排序也不是在一开始就进行的,而是在数据真的被访问时才会进行排序。因此OrderBy()在处理集合时,传递回来的是称为IOrderedEnumerable<T> 接口的对象。
OrderBy和ThenBy还有一个相似的方法,差别只在于做反向排序。OrderByDescending 和ThenByDescending。
观察函数的原型,会发现OrderBy传入的是IEnumerable<T> ,但ThenBy传入的是IOrderedEnumerable,所以一般在排序时先调用OrderBy,再使用ThenBy进行多重排序。假设一个集合有A和B两个属性,如果想要先为A排序再为B排序,则要使用OrderBy(A).ThenBy(B)的方式来进行排序,OrderBy和ThenBy 一次调用只能设置一个字段,在进行多重条件时,必须先调用OrderBy,再按需求调用ThenBy一次或多次。例:
var nameValues=new[]
{
new {name="Allen",value=64},
new {name="abbey",value=120},
new {name="slomng",value=330},
new {name="george",value=213}
};
//single sort
var sortedNames=nameValues.OrderBy(c=>c.name);
var sortedValues=nameValues.OrderBy(c=>c.value);
//multiply sort conditions
var sortedByNameValues=nameValues.OrderBy(c=>c.name).ThenBy(c=>c.value);
var sortedByValueNames=nameValues.OrderBy(c=>c.value).ThenBy(c=>c.name);
如果要设置多重排序条件,请务必使用OrderBy()加上ThenBy()的组合,若使用OrderBy +OrderBy 组合,会使得排序被执行两次,最终的结果会是最后一个OrderBy 所产生的的结果。
1.6、获取集合
LINQ 所处理的数据都由集合而来,因此将LINQ 执行的结果转换成集合也很容易。LINQ本身支持四种不同的集合生成方式,包含生成数组的ToArray()、生成列表的ToList、生成字典集合的ToDictionary 以及生成Lookup<TKey,TElement> 类的ToLookup。例:
var arrayOutput=nameValues.ToArray();
var listOutput=nameValues.ToList();
var dictOutput1=nameValues.ToDictionary(c=>c.name);
var dictOutput2=nameValues.ToDictionary(c=>c.name,c=>value);
1.7、划分并获取集合
Skip()、 SkipWhile()、 Take()、 TakeWhile()。在数据库查询时,为了达到最佳的性能,在数据量大时要进行分页处理(paging)。上面四个函数的功能就是在大集合内切出少量数据。
public static IEnumberable<TSource> Skip<TSource>(
this IEnumerable<TSource> source,
int count
)
public static IEnumberable<TSource> SkipWhile<TSource>(
this IEnumerable<TSource> source,
Func<TSource,bool> predicate
)
public static IEnumberable<TSource> SkipWhile<TSource>(
this IEnumerable<TSource> source,
Func<TSource,int ,bool> predicate
)
public static IEnumberable<TSource> Take<TSource>(
this IEnumerable<TSource> source,
int count
)
public static IEnumberable<TSource> TakeWhile<TSource>(
this IEnumerable<TSource> source,
Func<TSource,bool> predicate
)
public static IEnumberable<TSource> TakeWhile<TSource>(
this IEnumerable<TSource> source,
Func<TSource,int ,bool> predicate
)
Skip()用来在集合中跳跃,让LINQ 核心直接将游标跳到指定的位置,而不用通过“巡航”来移动,在大型集合中可节省不少时间,而SkipWhile 也有相同作用,但多了判断式,也就是跳过符合条件的元素,而不同的SkipWhile()可用来决定要跳过符合条件的或是判断跳过特定的索引值。
Take()用来传回集合中特定数量的元素,它会告知LINQ 核心直接返回它所指定的元素数量,很适合使用与分页的功能。TakeWhile 则是和SkipWhile 类似都是多了条件判断式,不过TakeWhile 在元素满足条件时,就返回该元素或是符合特定的索引值条件时返回该元素。
1.8、访问元素
IEnumerable<T>本身就是集合对象,所以针对集合对象所需要的元素访问也是必要的功能,LINQ里的元素访问功能是判断容器内是否含有元素等。
首先是获取首尾的元素,分别由First() 以及Last() 两个方法负责,它们还各有一个姐妹方法 FirstOrDefault() 以及 LastOrDefault() 前者若没有第一个或最后一个元素时,会传回null,而后者会传回其类型的默认值(基本上就是default(T)的结果)。
FirstOrDefault() 以及 LastOrDefault()都没有提供默认的设置方式,因此若想要使用非default(T)的默认值,要使用DefaultEmpty() 来设置。First() 和Last() 都能传入判断元素是否符合条件的参数,当条件判断存在时,First 会从集合的前面开始扫描,并返回扫描到符合条件的第一个元素,Last 则是反过来从集合的尾端开始扫描,并返回扫描到符合条件的第一个元素。例:
var firstLastItems=new []{"zero","two","three","four","five"};
string firstContainsO=firstLastItems.First(s=>s.Contains(\'o\'));
string lastContainsO=firstLastItems.Last(s=>s.Contains(\'0\'));
LINQ 内还有一个Single,他会在集合中只有一个元素时传回该元素,但若集合是空的或是有两个以上的元素时会调用例外处理,或是使用它的姐妹方法SingleOrDefault 传回null值,实用性比fisrt和last 低。
LINQ 提供了ElementAt() 这个方法,可按照索引值访问元素,他有个相似方法ElementAtOrDefault 作用和firstordefault/lastordefault 是相同的。当找不到元素时就返回默认值。例:
var firstLastItems=new []{"zero","two","three","four","five"};
string itematThree=firstLastITems.ElementAt(2);
若要判断集合内有没有特定值,LINQ 提供了Contains, 可以判断集合捏有没有传入的元素,但因为Contain 会判断对象是否相等,所以它另外提供了一个可传入IEqualityComparer<T> 的作为比较依据的重载(overload)方法,可用于自定义类对象的相等比较操作。
若要判断集合内有没有值,LINQ 提供了两个方法,一个是Count(), 另一个是Any(),除了可以简单判断集合内有没有值外,也可以传入判断条件来决定是否要列入计算。通常会习惯使用Count 来判断集合内是否存在任何元素,为什么要多做一个Any 呢。其实是考虑到LINQ 可能的查询对象会包含远程数据库,不一定只有本地的数据源。对于远程的数据源,如果使用Count ,要花费较高的成本来读取数据后进行计数在传回,但若是使用Any(),则远程只要判断符合条件的数据是否存在一笔即可,不需要完整计数,所以针对远程数据源,使用Any 来判断有无数据是较好的选择。针对本地的集合 any 和count 几乎没有差异。
若要判断集合内的元素是否全部符合特定条件时, 可以利用LINQ 的All(), 它可以按照传入的条件来扫描所有元素,只有在所有元素都符合条件时,或是集合时空时才会返回true ,否则会返回false。
若要按照元素的类型进行筛选的话,除了使用Where 对每个元素做类型信息判断外,LINQ 也提供了一个更简便的方法 OfType<T>(),它可以传回集合内符合T所指定类型的信息,这个方法很适合用在集合内包含了已实现了许多接口的类对象。然后使用OfType<T> 按照接口类型进行筛选。
OfType<T>还有一个类似方法Cast<T> ,功能与OfType <T> 相同,但Cast<T> 会试图把集合内的元素类型转换成T类型,若无法进行类型转换时会调用InvalidCastException 例外处理。若使用OfType<T>则不会引发例外处理。
1.9、聚合与汇总
聚合运算(aggregation)是集合数据处理的重要功能之一,基本的Max ,Min ,Sum , Average 以及可自己制定聚合规则的Aggregate()。
Aggregate 是可暂存每一步计算结果的方法,它允许程序员按照传入的条件对每个集合内的元素进行计算,而在每次调用时,他都会将前一次的结果暂存起来,并作为下次计算的传入参数。Aggregate 基本上做到三种工作,第一种是直接按照传入的条件来处理累计运算;第二种是可在调用时传入一个种子值(seed),这个种子值会在开始进行运算时作为基准使用,之后可按照第一次对种子值的运算方式开始做累计运算;第三种则是在传回之前做最后的处理,例:
double myBalance=100.0;
int[] withdrawItems={20,10,40,50,10,70,30};
double balance=withdrawItems.Aggregate(myBalance,(originbalance,nextWithdrawal)=>{
Console.WriteLine("originbalance:{0},nextWithdrawak:{1}",originbalance,nextdrawal);
Console.WriteLine("Withdrawal status:{0}",(nextWithdrawal<=originbalance)>"OK":"FAILED");
return ((nextWithdrawal<=originbalance)?(originbalance-nextWithdrawal):originbalance);
});
Console.WriteLine("Ending balance:{0}:",balance);
若要岁最终的存款值进行处理,即可使用第三个参数resultSelector,例:
var balanceStatus=
withdrawItems.Aggregate(myBalance,(originbalance,nextWithdrawal)=>{
return((nextWithdrawal<=originbalance)?(originbalance-nextWithdrawal):originbalance);
},
(finalbalance)=>
{
return (finalbalance>=1000)?"Normal":"Lower";
});
1、远程查询:IQueryable<T> 与IEnumerable<T>
在前面讨论的LINQ 中所有的功能,基本上都基于IEnumerable<T> 接口类型(实际的实现是由Enumerable<T>类实现的)来提供。IEnumerable<T>的特性是它一定要有一个实际存在的集合才可以操作,若没有这个集合会无法使用,这点在本地集合上基本上没有问题,但是远程数据源时,因为要实现IEnumerable<T> 的功能,势必要把远程的所有数据都读取到本地,数据量小的时候可能问题不大,但若是有几万笔、几十万的数据时,或变成很严重的问题。为了支持远程数据源,LINQ的IEnumerable<T> 是无法处理的,而必须要额外提供一个针对远程数据源的专用接口。
为了达到这个需求,微软在LINQ 内建立了一个介于IEnumerable<T> 和数据源之间的提供者,这个提供者在接到来自IEnumerable<T> 的操作时,会将LINQ 本身的指令转换成表达式,在经由远程数据源的查询提供者(Query provider)将表达式转换成远程数据源可执行的查询指令,最后交由远程数据源执行,但前段仍然使用LINQ 本身的方法,这个接口称为IQueryable<T>。在LINQ的核心实现中,有一个Queryable类,这个类也实现了与Enumerable类使用的是IEnumerable<T>接口。LINQ 要求所有向远程数据库查询的功能都要实现IQueryable<T>接口,大多数提供者都实现IOrderQueryable<T>接口。
当用户对IQueryable<T> 进行操作时,Queryable 类会通过调用IQueryable<T> 内的Provider属性所设置的IQueryProvider 接口实现进行数据访问的工作,IQueryProvider是LINQ Provider 的基类。只要是想支持LINQ 的远程(外部)数据源,都必须实现IQueryProvider 接口,以支持将LINQ的查询转换成远程数据查询的工作。
LINQ 中两个特别方法:AsEnumerable 与AsQueryable 。 AsEnumerable 是将IQueryable<T> 转换成IEnumerable<T>,而 AsQueryable 正好相反。当使用AsEnumerable 将IQueryable 转换成IEnumerable 时,就变成对内存内的集合进行操作了,而不需要将查询传回到远程数据源,速度回加快很多。但是,如果集合内没有元素的话,不会向远程数据库查询数据、相反使用AsQueryable 将IEnumerable 转换成IQueryable 时,所有的LINQ 操作都会被转换成对远程数据源的查询,而不对目前在内存的集合进行操作,所以真正的查询会在远程数据源进行,与本地的内存集合无关。
2、Expression
Expression 的功能实现于System.Linq.Expression 命名空间中,它记录了Expression表达式的结构,并提供了充分的信息让IQueryProvider 的开发人员访问其语法结构,以产生相对应的查询指令。可以把Expression表达式树的功能想象成一棵树,这棵树内包含了要运算的节点以及运算的方法(Expression Tree),而求解表达式时,只要使用中序遍历的方法,即可求解。
Expression 也是相同的原理,在使用IQueryable<T> 进行查询时,IQueryable<T>会将对IQueryable<T>所有的LINQ 进行调用,全部转换成Expression,这些Expression 会再经由LINQ Provider来转换成真正对数据源的查询指令,这些指令会包装成System.Linq.Expression 命名空间内的各种类。
3、LINQ 与ADO.NET : DataSet/DataTable 的使用
首先是DataTable,这里最常见的方法是 DataTable.Select(),然后用它规定的查询方式进行处理。CopyToDataTable<T>()可以将LINQ的查询结果转换成另一个DataTable。例:
DataSet ds=new DataSet();
ds.Locale=CultureInfo.InvariantCulture;
FillDataSet(ds);
DataTable orders=ds.Tables["SalesOrderHeader"];
IEnumerable<DataRow> query=
from order in orders.AsEnumerable();
where order.Field<DateTime>("OrderDate")>new DateTime(2001,8,1)
select order;
DataTable boundTable=query.CopyToDataTable<DataRow>();
bindingSource.DataSource=boundTable;
下面介绍DataRow 本身,在一开始DataRow 基本上提供松散类型的数据容器,因此在处理查询时都要先转换才能查询,LINQ 推出后,对DataRow类型进行了扩展,扩展的功能是强化它的强类型功能,以作为查询时简化类型转换之用,它们分别是Field<T>() 以及SetField<T>()两个方法。这两个方法都是重载(overload),可根据需求来调用必要的方法。例:
DataSet ds=new DataSet();
ds.Locale=CultureInfo.InvariantCulture;
FillDataSet(ds);
DataTable products=ds.Tables["Product"];
var query=
from product in products.AsEnumerable()
where product.Field<string>("color")=="Red"
select new
{
Name=product.Field<string>("Name"),
ProductNumber=product.Field<string>("ProductNumber"),
ListPrice=product.Field<Decimal>("ListPrice")
};
foreach(var product on query)
{
Console.WirteLine("Name:{0}",product.Name);
.....
}
除了以强类型访问DataRow 的字段外,微软也提供了强类型比较的DataRowComparer类,以处理当使用强类型方式查询DataRow 使所需要的类型转换处理工作。
var contacts=contacts1.AsEnumerable().Intersect(contacts2.AsEnumerable(),DataRowComparer.Default);
MVC 5 网站开发之美笔记 LINQ:推迟查询的执行作者: 殷熙 |
1、推迟查询的执行
在运行期间定义查询表达式时,查询就不会运行,查询会在迭代数据项时运行。
using System;
using System.Collections.Generic;
using System.Linq;
namespace OKITEST
{
class Program
{
static void Main(string[] args)
{
var names = new List<string>{ "Nino","Alberto","Juan","Mike","Phil"};
var namesWithJ = from n in names
where n.StartsWith("J")
orderby n
select n;
Console.WriteLine("First iteration");
foreach(var name in namesWithJ)
{
Console.WriteLine(name);
}
Console.WriteLine();
names.Add("John");
names.Add("Jim");
names.Add("Jimmy");
Console.WriteLine("Second iteration.");
foreach(var name in namesWithJ)
{
Console.WriteLine(name);
}
}
}
}
运行结果如下:
因为迭代在查询定义时不会进行,而是在执行每个foreach 语句时进行,所以可以看到如上的结果。
当然,还需要注意,每次在迭代中使用查询时,都会调用扩展方法。在大多数情况下,这是非常有效的,因为我们可以检测出元数据中的变化。但是在一些情况下,这是不可行的,调用扩展方法ToArray(),ToList()等可以改变这个操作。
在下面的例子送,ToList 遍历集合。返回一个实现了IList<string> 的集合。之后对返回的列表遍历两次。在两次迭代之间,数据源得到了新名称。
using System;
using System.Collections.Generic;
using System.Linq;
namespace OKITEST
{
class Program
{
static void Main(string[] args)
{
var names = new List<string>{ "Nino","Alberto","Juan","Mike","Phil"};
var namesWithJ = (from n in names
where n.StartsWith("J")
orderby n
select n).ToList();
Console.WriteLine("First iteration");
foreach(var name in namesWithJ)
{
Console.WriteLine(name);
}
Console.WriteLine();
names.Add("John");
names.Add("Jim");
names.Add("Jimmy");
Console.WriteLine("Second iteration.");
foreach(var name in namesWithJ)
{
Console.WriteLine(name);
}
}
}
}
结果如下:
以上是关于C#中的LINQ的主要内容,如果未能解决你的问题,请参考以下文章