LINQ&EF任我行(二)--LinQ to Object
(原创:灰灰虫的家http://hi.baidu.com/grayworm) LinQ to Objects是LinQ家庭的核心,其它的LinQ也使用了与LinQ to Objects相同的查询句法。最终编译器都是把LinQ句法翻译成扩展方法的链式表达式,同时把扩展方法中的lambda表达式转换成匿名类中的匿名方法,然后再把查询表达式编译成MSIL。 LinQ to SQL、LinQ to DataSets、LinQ to Entities和LinQ to XML则不是把查询表达式转换成MSIL,而是把查询表达式树转换成相应的特定查询语言。LinQ to SQL会生成T-SQL,LinQ to Entities会生成eSQL,LinQ to XML会生成XPath语句等。
LinQ标准查询操作符列表
在VS2008及以后的版本中提供了LinQ的查询样例程序。 \Programe Files\Microsoft Visual Studio 2008(10.0 VS2010)\Samples\1033文件夹下有个压缩包CSharpSamples.zip,解压它,继续打开其中的文件夹 .\LinqSamples\SampleQueries\SampleQueries.sln,打开此解决方案,运行该项目,界面如下
在左边选择示例,右侧上方会出现相应的LinQ代码,右侧下方会出现代码的运行结果。通过这个样例程序,我们可以学习LinQ的各种用法。
下面我们来学习LinQ常用操作符 一、筛选操作符Where 根据谓词对源序列的内容进行筛选,类似于SQL中的where子句。
1.简单where表达式 使用扩展方法 var query1 = CustomerList.Where(c => c.Country == “USA”); 使用查询表达式语法 query1 = from c in CustomerList where c.Country == “USA” select c; 使用扩展方法需要向where方法中传入lambda表达式。
2.复合where表达式 所谓的复合where表达式就是使用&&或||操作符对数据进行筛选 使用扩展方法 var query2 = CustomerList.Where(c => c.Country == “USA” && c.Orders.Any()); 使用查询表达式语法 query2 = from c in CustomerList where c.Country == “USA” && c.Orders.Any() select c;
Any()方法相当于Count()>0,如果集合中有元素就返回true。
3.使用Index参数和IndexOf()方法 index值代表的是集合中元素的索引号,在where()中使用index可以控制返回与指定索引号相关的集合数据。泛是返回IEnumerable<T>类型的查询操作都可以使用index。 使用扩展方法 var query3 = CustomerList.Where((c, index) => c.Country == “USA” && index > 70);//返回国家是USA,索引号大于70的元素集合。 这里lambda表达式中接收的参数是(c,index),索引号做为第二个参数传入。
也可以使用IndexOf()方法来实现上面的功能,如果使用IndexOf()的话,那lambda表达式中传入的参数只需要一个就可以了。 使用扩展方法 var query3 = CustomerList.Where(c => c.Country == “USA” && CustomerList.IndexOf(c) > 70); 使用查询表达式语法 query3 = from cust in CustomerList where cust.Country == “USA”&& CustomerList.IndexOf(cust) > 70 select cust; 二、投影运算符 投影运算符对应SQL中的“select 列名”子句
(一)Select Select操作符是从序列源返回一组指定属性 使用扩展方法 var infos = context.Infos.Where(p => p.Sex == true).Select(p => new { p.Name,p.Sex}); foreach (var c in infos) { Console.WriteLine(c.Name + c.Sex); } 使用查询表达式语法 var infos = from p in context.Infos where p.Sex == true select new { p.Name, p.Sex }; foreach (var c in infos) { Console.WriteLine(c.Name + c.Sex); } 如果使用扩展方法则在Select()方法中使用lambda表达式p=>new {p.Name,p.Sex}来对列进行投影;如果使用查询表达式语法的话,直接在select关键字后使用匿名类new { p.Name, p.Sex }即可。
(二)SelectMany SelectMany操作符实际上实现的是相关数据的交叉连接操作。它根据lambda表达式从一对多的序列中返回指定的属性。 比如:
《图3》 查询男员工的所有家庭成员: 使用扩展方法 var q = context.Infos.Where(p => p.Sex == true).SelectMany(f=>f.Families); foreach (var n in q) { Console.WriteLine(n.Name); } 使用查询表达式语法 var q = from p in context.Infos where p.Sex==true from f in p.Families select f; foreach (var n in q) { Console.WriteLine(n.Name); }
三、分块操作符 (一)Skip和Take Skip是从序列中跳过元素的个数;Take是从序列中获取元素的个数; 如:跳过集合的前2个元素,从第三个元素开始向后取4个元素。 使用扩展方法 var q = list.Skip(2).Take(4); 使用查询表达式语法 var q = (from p in list select p).Skip(2).Take(4);
(二)SkipWhile和TakeWhile SkipWhile:条件跳过,从序列第一个元素开始依次判断,一直跳到不满足条件的元素为止,返回此元素及此元素之后的序列 ; TakeWhile:条件抓取,从序列第一个元素开始依次判断,只要满足条件就进行下个元素判断,直到不满足条件的元素为止,返回此元素之前的序列 ; 如:取集合中第一批“性别”是“男”的元素的集合。 使用扩展方法 var q = list.SkipWhile(p => p.Sex == false).TakeWhile(p => p.Sex == true); 使用查询表达式语法 var q = (from p in list select p).SkipWhile(p => p.Sex == false).TakeWhile(p => p.Sex == true);
四、连接运算符 Join和GroupJoin操作符是把两个相互独立的对象通过关键属性关联起来。这种对象与对象的关联与SQL中的Join关联语法上有些不同。 1.LinQ的Join不支持SQL-92中的一些比较运算符,如>、<、<>等。它只支持相等运算符 2.在On子句中不能使用=来实现两个对象之间的关联,需要使用Equals运算符。 (一)Join 使用扩展方法 var list = infos.Join(works, p => p.Code, w => w.InfoCode, (p, w) => new { p.Name, w.Firm, w.Depart }); 使用查询表达式语法 var list = from p in infos join w in works on p.Code equals w.InfoCode select new { p.Name, w.Firm, w.Depart }; (二)GroupJoin 可以实现外联效果 Join扩展方法与GroupJoin扩展方法签名有些不一样
在C#3.0查询表达式语法中没有GroupJoin语法,可以使用Join...into...来实现,它与Join不同的是,它可以实现类似于SQL外联接的效果,而Join只实现类似于SQL内联的效果。 使用查询表达式语法 var list = from p in infos join w in works on p.Code equals w.InfoCode into bo from r in bo.DefaultIfEmpty() select new { p.Name, r};
五、连接运算符 Concat运算符用来把两个序列连接到一个序列中,它类似于SQL中的关系或or运算符。 使用扩展方法 var q = infos.Select(p=>p.Name).Concat(works.Select(w=>w.Firm)); 使用查询表达式语法 var q = (from p in infos select p.Name).Concat(from w in works select w.Firm);
六、排序运算符 排序运算符一共包含五个运算符OrderBy、OrderByDescending、ThenBy、ThenByDescending和Reverse OrderBy:升序排序 OrderByDescending:降序排序 ThenBy:在OrderBy或OrderByDescending后实现多级排序中实现升序排序 ThenByDescending:在OrderBy或OrderByDescending后实现多级排序中实现降序排序 Reverse:顺序倒转
如:对所有人员先按照性别升序排序,再按照生日降序排序 使用扩展方法: var q = infos.OrderBy(p => p.Sex).ThenByDescending(p => p.Birthday); 使用查询表达式语法: var q = from p in infos orderby p.Sex,p.Birthday descending select p; 在查询表达式语法中实现多级排序类似于T-Sql中的方式。
七、分组操作符 分组操作符GroupBy用来按照元素的某个属性来对序列中的元素进行分组。类似于SQL中的group by 子句,但它是对象的序列,还可以获取每组中的每个元素对象。 如:按照性别对人员进行分组,并显示每组中人员的信息 使用扩展方法: var q = infos.GroupBy(p=>p.Sex);; 使用查询表达式语法: var q = from m in infos group m by m.Sex into g select g; 显示分组数据: foreach (var item in q) { //item.Key代表分组后的关键字的值,在这里是性别Sex的值 Console.WriteLine("性别为"+item.Key+"\t共有"+item.Count()+"人"); foreach (var c in item) { Console.WriteLine("\t" + c.Name + "\t" + c.Sex); } Console.WriteLine("*********************"); }
八、集合操作符 集合操作符包括Distinct、Union、Intersect和Except,除了Distinct之外其余的三个是用来把两个集合拼合成一个集合。 (一)Distinct Distinct操作符用来把序列中重复的值移除掉,类似于SQL中的Distinct 如:查看Infos集合中所有 使用扩展方法: var q = infos.Select(p => p.Nation).Distinct(); 使用查询表达式语法: var q = (from p in infos select p.Nation).Distinct();
(二)Union Union操作符取两个具有相同结构的集合并集,如果两集合中有相同元素,则会自动滤去重复内容。而前面所讲的Concat操作符只是将两个集合进行合并,并不过滤重复元素。 如:两个集合,其中temp是infos的子集。 var infos = from p in context.Infos.ToList() select p; var temp = infos.Where(p => p.Sex == true); 使用扩展方法: var q = infos.Union(temp); 使用查询表达式语法: var q = (from p in infos select p).Union(from m in temp select m); 运行结果中,子集的内容并没有重复出现
(三)Intersect Intersect操作符是取两个具有相同结构的集合的交集部份。 如:两个集合,其中temp是infos的子集。 var infos = from p in context.Infos.ToList() select p; var temp = infos.Where(p => p.Sex == true); 使用扩展方法: var q = infos.Intersect(temp); 使用查询表达式语法: var q = (from p in infos select p).Intersect(from m in temp select m); 运行结果中只显示子集中的内容。
(四)Except Except操作符是从一个集合中取另一个集合的差集,即从集合A中取出集合B中不包含的元素。 如:两个集合,infos和temp,temp集合中包含了infos集合中不存在的元素 var infos = from p in context.Infos.ToList() select p; var temp = infos.Where(p => p.Sex == true).ToList(); temp.Add(new Info { Code = "p100", Name = "哈哈", Sex = false, Nation = "n004", Birthday = DateTime.Now }); 使用扩展方法: var q = infos.Except(temp); 使用查询表达式语法: var q = (from p in infos select p).Except(from m in temp select m);
九、转换操作符,用来改变集合的类型 (一)ToArray 把集合转换为数组形式,不延迟 使用扩展方法: Info[] g = infos.ToArray(); 使用查询表达式语法: Info[] g = (from p in infos select p).ToArray(); (二)ToList 把集合转换为泛型集合形式,不延迟 使用扩展方法: List<Info> g = infos.ToList(); 使用查询表达式语法: List<Info> g = (from p in infos select p).ToList();
(三)ToDictionary 把集合转换成Dictionary<TKey,TElement>类型的集合,它每个元素的value值是原集合中的一个元素对象。 如:下面的代码把集合的内容转换为一个字典集合,字典的key值是人员代号,字典的value值是info元素对象。 使用扩展方法: var q = infos.ToDictionary(p=>p.Code); 使用查询表达式语法: var q = (from p in infos select p).ToDictionary(p=>p.Code); 调用语法 foreach (var item in q) { Console.WriteLine(item.Key+item.Value.Name); } 从运行结果中我们可以看出,字典的第二个参数就是原集合中的元素。
(四)ToLookup 把集合转换成ILookup<TKey,TElement>类型的集合,ILookup<TKey,TElement>集合与Dictionary<TKey,TElement>集合不同的是:Dictionary<TKey,TElement>中Key和Value值一一对应,而ILookup<TKey,TElement>集合中Key和Value值是一对多的对应关系。 如:使用ILookup<TKey,TElement>把集合中的元素分组显示 使用扩展方法: var q = infos.ToLookup(p=>p.Nation); 使用查询表达式语法: var q = (from p in infos select p).ToLookup(p => p.Nation); 显示数据的代码: foreach (var item in q) { Console.WriteLine(item.Key); foreach (var single in item) { Console.WriteLine("\t" + single.Name); } } 显示效果:
十、相等操作符:SequenceEqual 用来对两个序列进行对比。如果所有元素的值相等,并且元素个数相等,并且元素的次序相等,那SequenceEqual操作符返回的是True,否则返回False var s1 = infos.OrderBy(p => p.Code); var s2 = infos.OrderByDescending(p=>p.Code); var s3 = infos.OrderBy(p => p.Code); Console.WriteLine(s1.SequenceEqual(s2)); //结果是False Console.WriteLine(s1.SequenceEqual(s3)); //结果是True
十一、元素操作符: 元素操作符的作用是从IEnumerable<T>集合序列中返回一个指定的元素。 如果没有找到指定的元素,所有的XXXDefault操作符返回空对象,并不会产生异常。而First、Last、Single和ElementAt操作符则会产生异常。
(一)First和FirstOrDefault 如果序列中包含一个或多个元素,这两个操作符返回序列中的第一个元素。如果序列不包含任何元素,则FirstOrDefault操作符返回null值(引用类型)或默认值(值类型),而First操作符则产生异常信息。 如:查找第一个女生 使用扩展方法: var item = infos.First(p=>p.Sex == false); var item = infos.Where(p => p.Sex == false).First(); 使用查询表达式语法: var item = (from p in infos where p.Sex == false select p).First();
(二)Last和LastOrdefault 如果序列中包含一个或多个元素,这两个操作符返回序列中的最后一个元素。如果序列不包含任何元素,则LastOrDefault操作符返回null值(引用类型)或默认值(值类型),而Last操作符则产生异常信息。 如:查找最后一个女生 使用扩展方法: var item = infos.Last(p=>p.Sex == false); var item = infos.Where(p => p.Sex == false).Last(); 使用查询表达式语法: var item = (from p in infos where p.Sex == false select p).Last();
(三)Single和SingleOrDefault 如果序列中有且只有一个元素,则这两个操作符返回该元素 如果序列中没有任何元素,则Single会产生异常,而SingleOrDefault则会返回null值(引用类型)或默认值(值类型) 如果序列中包含多个元素,则这两个操作符都会产生异常。
(四)ElementAt和ElementAtOrDefault 这两个操作符是根据索引号从序列中返回指定的元素,如果未找到元素ElementAt()会产生异常,而ElementAtOrDefault()则会返回默认实例。 如:取出集合中的第二个女生 使用扩展方法: var item = infos.Where(p=>p.Sex == false).ElementAt(2); 使用查询表达式语法: var item = (from p in infos where p.Sex == false select p).ElementAt(2);
十二、元素数量操作符 判断序列中元素是否满足指定的条件返回bool型的值。带有该操作符的话句不能实现延迟查询,语句会被立即执行。 (一)Any 如果序列中存在任一个满足条件的元素,就返回true 如:判断是否存在代号为P005的人员 使用扩展方法: var q = infos.Any(p=>p.Code == "p005"); var q = infos.Where(p => p.Code == "p005").Any(); 使用查询表达式语法: var q = (from p in infos where p.Code == "p005").Any();
(二)All 如果序列中所有元素都满足条件,就返回true 如:判断是否所有员工都是汉族 使用扩展方法: var q = infos.All(p => p.Nation == "汉族"); 使用查询表达式语法: var q = (from p in infos select p).All(p=>p.Nation == "汉族");
(三)Contains 判断集合中是否包含指定的元素
十三、聚合操作符 聚合操作符类似于SQL中的聚合函数,所有带有聚合操作符的LinQ语句都无延迟功能,会被立即被执行。 (一)Count 取得序列中满足条件的元素的个数 使用扩展方法: var q = infos.Count(p => p.Sex == false); var q = infos.Where(p => p.Sex == false).Count(); 使用查询表达式语法: var q = (from p in infos where p.Sex==false select p).Count(); (二)Min、Max、Sum和Average 分别是取得序列中所有元素中某属性的最小值、最大值、总和、平均值等。 decimal? minFreightCharge = OrderList.Min(c => c.Freight); decimal? maxFreightCharge = OrderList.Max(c => c.Freight); decimal? sumFreightCharge = OrderList.Sum(c => c.Freight); decimal? avgFreightCharge = OrderList.Average(c => c.Freight);
总结: 这一篇文章主要介绍了LinQ的标准查询操作符(SQO)。对每个操作符都介绍了使用扩展方法的使用和查询表达式的使用,对于这两种用法大家应当都掌握住,尤其要记住扩展方法的使用。 由于时间的原因没有对每个示例的运行结果抓图显示,朋友们可以自己测试一下结果。 上面的内容是LinQ to Object的基础语法,也是其它LinQ的基础,熟练使用这些操作符能够使我们在LinQ天地中自由驰骋。