最近和我们老大一起做技术面试(我是旁听的),发现前来面试的没几个掌握甚至是丁点了解LINQ。这让我很纳闷,LINQ伴随2008一起发布至今难道大家真的没时间去了解一下或者学习一下这个应用基础吗。甚至问及有些人LINQ是什么,答题者想都不想 LINQ TO SQL, 崩溃!没错,LINQ是可以TO SQL,但是除了SQL,LINQ就无所作为了?非也。因此在这里和大家一起分享学习LINQ。本文适合以下读者, 如果你是不符合者请赏脸捧个场,3Q
- 从未触碰过LINQ的
- 对LINQ有过了解但是从未实战过的
- 打算学习LINQ的
简 介
LINQ 是什么?引用官方术语“语言集成查询 (LINQ) 是 Visual Studio 2008 和 .NET Framework 3.5 版中引入的一项创新功能,它在对象领域和数据领域之间架起了一座桥梁。” 那么LINQ给我们带来了什么,请看以下例子:
问:有序列A=int[]{1,2,3,4,5,6,7,8,0}; B=int[]{2,4,7,8,9}。请求出包含A和B共同值的序列C。
如果按照原来的思路,那么编码也许如下:
List<int> c = new List<int>(); foreach(int a in A){ foreach(int b in b) { if (a==b) {
c.add(a);
} } }
是不是觉得上面这段虽然没什么问题,但是很丑陋。如果我们引用LINQ来编写呢:
IEnumerable<int> C = from a in A from b in B where a==b select a;
是不是觉得很爽,一句话简单的把上面丑陋的代码给KO掉了。也许你看不懂上面的语法,没关系请继续往下阅读。
语 法
1. LINQ所处在的主要命名空间:System.Linq
2. LINQ的处理的核心对象就是IEnumerable可枚举对象也包括了泛型枚举,换句话说当你要处理的对象为IEnumerable类型对象时即可使用LINQ操作它。且在没有经过其他处理的情况下将返回一个新的IEnumerable序列,注意LINQ有一个特性“延迟加载”这个将在后续说明。
3. 关键字(摘自MSDN):
from : 指定数据源和范围变量(类似于迭代变量)。
where: 根据一个或多个由逻辑“与”和逻辑“或”运算符(&& 或 ||)分隔的布尔表达式筛选源元素。
select: 指定当执行查询时返回的序列中的元素将具有的类型和形式。
group: 按照指定的键值对查询结果进行分组。
into: 提供一个标识符,它可以充当对 join、group 或 select 子句的结果的引用。
orderby: 基于元素类型的默认比较器按升序或降序对查询结果进行排序。
join: 基于两个指定匹配条件之间的相等比较来联接两个数据源。
let: 引入一个用于存储查询表达式中的子表达式结果的范围变量。
in: join 子句中的上下文关键字。
on: join 子句中的上下文关键字。
equals: join 子句中的上下文关键字。
by: group 子句中的上下文关键字。
ascending:orderby 子句中的上下文关键字。
descending:orderby 子句中的上下文关键字。
4. 语法说明,每个LINQ语句都以from作为开头,以select作为结束,这点和T-SQL语法不通的切记先入为主的思考。其他关键字如where则类似T-SQL作为筛选判断条件。
样例:IEnumerable<T> nums = from n in nums where .... orderby... select....
扩 展
从 .net 3.0 开始 MS 就给我们引进了其他一些新的特性,由于篇幅关系在这里给大家简单的介绍几个LINQ常用到的特性:
1. 关键字 var :
指示编译器根据初始化语句右侧的表达式推断变量的类型。 推断类型可以是内置类型、匿名类型、用户定义类型或 .NET Framework 类库中定义的类型。这样我们就可以在上述的LINQ表达式中 例如可简写为: var nums = from n in nums where .... orderby... select....
2. 匿名类型:
匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。 类型名由编译器生成,并且不能在源代码级使用。 每个属性的类型由编译器推断。例如:var obj = new {A="a", B="b"}; 而LINQ则可以为 var nums = from obj in objs select new {obj.A, obj.B}
案 例
普通查询
var query = from num in num select num.ProperyA
筛选查询
var query = from obj in objs where obj.ProperyA > Condition select obj
分组查询
var query = from obj in objs group obj by obj.PropertyA into g orderby g.Key select g;
注意,在此示例里,关键字 into 不是必须的,使用 into 时,必须继续编写该查询,并最终用一个 select 语句或另一个 group 子句结束该查询。
内联查询
var query= from obj1 in objs1 join obj2 in objs2 on obj1.ID equals obj2.ID select new { A= obj1.Property, B = obj2.Property };
左外联查询
var query = from obj1 in objs1 join obj2 in objs2 on obj1.ID equals obj2.Obj1ID into g from subpet in g.DefaultIfEmpty() select new { P1 = obj1.P1, P2 = (subpet == null ? null : subpet.P2 ) };
注意,此处涉及到.net 3.5 新特性静态扩展方法(后续介绍不影响理解)DefaultIfEmpty():如果序列为空,则返回一个具有默认值的单一实例集合
总 结
本文到此,对LINQ的入门进行了简单介绍。你可以进行简单的实战应用了。后续将和大家一起分享学习 什么是静态扩展方法,它与LINQ有什么关系。LINQ的涉及应用如:linq to sql, lint to entites, linq to xml, linq to dataset 等等。敬请期待。
最后感谢阅读,有说得不对的地方请多多指正。
资 源
linqpad:一个学习LINQ的好工具http://www.linqpad.net/
linq开发资源:http://www.codeproject.com/KB/linq/
到现在为止你还未触碰LINQ,那进来吧 —— LINQ入门(中篇)前 言在上篇中简单的分享了LINQ的基础概念及基础语法,如果没有阅读过上篇的朋友可以点击这里。感谢大家的支持,本篇我们将更进一步的学习LINQ的一些相关特性及应用方法。废话不多说,请往下阅读吧。 延迟加载在上篇中简单的和大家提到了LINQ具有一个很有意思的特性那就是“延迟加载”(或“延迟计算”),什么是延迟加载呢?先看来自官方的描述:延迟执行意味着表达式的计算延迟,直到真正需要它的实现值为止。是不是觉得有点生涩难理解呢?按照我个人的理解通俗的讲就是,每当我们编写好一段LINQ表达式时,此时这个表达式所代表的序列变量仅仅只是一个代理,编译器在执行编译时根本就不鸟这段代码,检查完语法正确性后直接跳过,直到代码在编译器动态运行序列变量在其他代码块被调用时,它所代理的linq表达式才会执行。啊~~看到这里你是不是要晕了,到底要怎么理解啊,无废话上代码:
1 // 已知一个序列 2 var array = new int[] {1, 2, 3}; 3 4 // 编写一段LINQ表达式获得一个序列变量query 5 // 注意,这个变量仅仅是一个代理,在执行编译的时候,编译器检查完 6 // 该代码的正确性后直接跳过,不再理会 7 var query = from arr in array 8 where arr > 1 9 select arr; 10 11 // 调用上述序列变量query,此时上述的LINQ表达才会执行。注意此时已是在 12 // 编译器Runtime 的情况下执行 13 foreach(var q in query) 14 Console.WriteLine(q.ToString());
如果你觉得上述例子不能让你有个深刻的理解,那么请看来自MSDN的例子 1 public static class LocalExtensions 2 { 3 public static IEnumerable<string> 4 ConvertCollectionToUpperCase(this IEnumerable<string> source) 5 { 6 foreach (string str in source) 7 { 8 Console.WriteLine("ToUpper: source {0}", str); 9 yield return str.ToUpper(); 10 } 11 } 12 } 13 14 class Program 15 { 16 static void Main(string[] args) 17 { 18 string[] stringArray = { "abc", "def", "ghi" }; 19 // 这里方法 ConvertCollectionToUpperCase 是不会在编译时进行调用核查的,直到下面的foreach调用变量 q 此方法才会执行 20 var q = from str in stringArray.ConvertCollectionToUpperCase() 21 select str; 22 23 foreach (string str in q) 24 Console.WriteLine("Main: str {0}", str); 25 } 注意,ConvertCollectionToUpperCase 是一个静态扩展方法,后续讲解,如果你对.net 2.0 的 yeild 不熟悉的网上查阅吧,这里就不做介绍了。 // 输出结果 // ToUpper: source abc // Main: str ABC // ToUpper: source def // Main: str DEF // ToUpper: source ghi // Main: str GHI 小结,延迟加载有好也有坏,由于是在Runtime的情况下执行序列,所以就容易造成未知异常,断点打错等等,所以编码LINQ是一定要考虑到它的这个特性。 lambda 表达式了解完延迟加载后,那么现在我们需要简单的学习一下.net 3.5 给我们带来的新特性lambda表达式,在上篇的评论中,有园友问lambda和linq有什么关系,在这里其实他们没有任何关系,是完全不同的东西,但是我们为什么要掌握它呢?因为在后续的学习中会使用大量的lambda表达,他可以使我们的代码更优雅更有可读性,大大提高了我们的编码效率。 那么在学习lambda之前,先来回顾一下.net 2.0给我们带来的委托 delegate ,这个你一定不会感到陌生吧,而且一定会常用他。对于委托这里就不做详细的介绍了,要复习委托的在网上查阅吧。通过委托,我们可以得到一个东西“匿名方法”。咦,是不是觉得很眼熟,呵呵,用代码来加深回忆吧 public delegate void SomeDelegate1; public delegate void SomeDelegate2(arg 1, arg 2); // 匿名方法 SomeDelegate1 del1 += delegate() {...}; SomeDelegate2 del2 += delegate(arg1, arg2) {...} 上面的代码中我们看到在.net 2.0时代,我们可以通过delegate创建匿名方法,提高编码的灵活性,那么lambda和这个有什么关系呢,lambda对匿名方法进行了升华。看代码: public delegate void SomeDelegate1; public delegate void SomeDelegate2(arg 1, arg 2); // 匿名方法 SomeDelegate1 del1 += () => {...}; SomeDelegate2 del2 += (arg1, arg2) => {...} 呵呵,是不是觉得有点不可思议呢,言归正传什么是lambda表达式呢,来自官方的定义:“Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式树类型。所有 Lambda 表达式都使用 Lambda 运算符 =>,该运算符读为“goes to”。 该 Lambda 运算符的左边是输入参数(如果有),右边包含表达式或语句块。 Lambda 表达式 x => x * x 读作“x goes to x times x”。在定义里提到了表达式树,这是高阶晋级的话题,这里就不做讨论了,我们先把精力放在入门与实战应用上。 常规的lambda表达式如下:
(parameters) => {expression}
当指定的委托类型没有参数是表达式可以如下
() => {expression} 例:() => {/*执行某些方法*/}
如果表达右侧花括号里只有一个表达例如一元表达式,二元表达式等等,又或者是一个方法时那么花括号可以省略如下: (x) => x; // 最简表达式 (x, y) => x == y; () => SomeMethod(); 注意,如果右侧的表达式存在花括号"{}",而且委托是具有返回类型的,那么表达式必须带上 return 关键字,如下: (x, y) => {return x == y;};
到此我们已对lambad 表达式有了一定的掌握与了解。那么我们扩展一下,在.net 3.5中,ms 给我们提供了两个泛型委托 分别是 Fun<T> 和 Action <T> 他们可以帮助我们省去了返回创建常用委托的麻烦,提高编码效率。 共同点:它们至多提供委托传递6个参数(任意类型); 不同点:Fun 要求必须具有返回类型,而Action则必须不返回类型,规定返回 void 示例: Fun<int, int, bool> fun = (a, b) => a==b; Action<string> action = (p) => Console.Write(p); 小结,lambda 对我个人而言是个又爱又恨啊,不过爱多一点,它使我们写更少的代码做更多的事,但是在调试时一旦修改表达式内容,那么当前调试要么停止,要么重新开始,ms要是在这方面做得更完美些就好啦。不过它也间接提醒我们要有好的编码设计思维。 静态扩展方法说完lambda,那么我们就进一步了解一下.net 3.5的另一个新特性“静态扩展方法”,什么是静态扩展方法呢,官方定义:扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。简单的说就是我们可以向一个已知的类型在不通过继承,复写等操作的情况下添加一个方法,以便类型的实例可以直接使用该方法。示例如下:
static class A { // 这里的p1,p2 仅作为示例,实际中我们不一定需要 public static int ExtendMethod1(this string input,string p1, string p2) { return int.Parse(input + p1 + p2); } // 泛型方法 public static TOutput, ExtendMethod2<TOutput>(this object obj); { return (TOutput)obj; } }
注意,方法的 static 是必须的,而且需在静态类里。第一个参数 this 是必须的,紧跟着 this 后面的需要扩展的类型实例参数,也是必须的。至于后面的方法调用传递参数就因个人所需了。 既然我们学习了静态扩展方法,那么它和LINQ又有什么关系呢?在System.Linq的命名空间中提供了大量的静态扩展方法,这些静态方法本身就对linq表达式的封装,这样我们就可以省去了编写简单的linq表达式的步骤。如下:
var array = new int[]{1,2,3,4,5}; var query1 = from arr in array select arr; var query2 = array.Select(e => e);
上面的示例中 query1 和 query2 是等价的,通过 query2 我们是不是又可以偷懒了很多,呵呵。 再来点带where的 var array = new int[]{1,2,3,4,5}; var query1 = from arr in array where arr > 2 select arr; var query2 = array.Where(e => e > 2); 再来一个复合型的 var array = new int[]{1,2,3,4,5}; var max = (from arr in array.Where(e => e > 2) select arr).Max(); 是不是觉得很cool。由于篇幅的关系在这里就不逐一的去接受这些静态方法了,下面是一些常用的静态方法列表,感兴趣的去MSDN查阅详细吧。 Aggregate , All , Any , AsEnumerable , Average , Cast , Concat , Contains, Count, DefaultIfEmpty , Distinct , ElementAt, ElementAtOrDefault ,Empty , Except , First, FirstOrDefault , GroupBy , GroupJoin , Intersect , Join , Last , LastOrDefault , LongCount , Max , Min , OfType ,OrderBy ,OrderByDescending , Range , Repeat , Reverse , Select , SelectMany , SequenceEqual , Single , SingleOrDefault , Skip , SkipWhile , Sum ,Take, TakeWhile , ThenBy , ThenByDescending , ToArray , ToDictionary , ToList, ToLookup, Union,Where Cast<T> 和 OfType<T> 静态扩展方法在最后我们还是要注意两个常用的静态方法Cast<T>, OfType<T> 。它们的共同点是都能把非IEnumerable<T> 类型的集合转换成IEnumerable<T>类型,然后再 进行LINQ操作,如下 var dt = new DataTable(); dt.Columsn.Add("A", typeof(int)); var newRow1 = dt.NewRow(); newRow1["A"] = 1; var newRow2 = dt.NewRow(); newRow2["A"] = 2; dt.Rows.Add(newRow1); dt.Rows.Add(newRow2); var query1 = dt.Rows.Cast<DataRow>().Select(e=>(int)e["A"]); var query2 = dt.Rows.OfType<DataRow>().Select(e=>(int)e["A"]); 这样我们就可以得到看上去两个相同的序列,在这里要注意:MSDN上的说明存在误导,MSDN对于OfType<T>的解释存在偏差,实际上经本人反复敲代码验证,得到的结论是,Cast对序列进行强制转换,一旦转换不成功则抛出异常。OfType则是一旦转换不成功,则不会抛出异常,但是将会得到一个空序列。见下面代码: var arr1 = new string[] { "1","2","test" }; var arr2 = arr1.Cast<int>(); var arr3 = arr1.OfType<int>(); //通过Cast转换,则会抛出异常 foreach (var i in arr2) Console.WriteLine(i.ToString()); //通过OfType转换,有异常但是不会抛出并得到一个空序列 foreach (var i in arr3) Console.WriteLine(i.ToString()); Console.Read();
总 结本文到此,我们已对LINQ涉及的应用有了进一步的了解。学习什么是linq的延迟加载,lambda和linq是否有暧昧关系。以及静态扩展方法对linq的辅助作用。也许你会问既然可以用静态扩展方法替代编写linq,那么二者怎么择取呢,据砖家叫兽提议我们应该先以linq命名空间下的静态扩展方法为主,实在是很复杂的linq表达式,我们再考虑使用linq本身的表达式编写。后续我们将分享学习LINQ更贴近实战应用的知识,linq to dataset, linq to xml, linq to sql, linq to entities. 感谢您的阅读,如果有说得不对的地方请指正。 | |
firefly
天意无私
|
到现在为止你还未触碰LINQ,那进来吧 —— LINQ入门(下篇)前 言终于来到下篇了,通过上篇,和中篇,我们了解了linq的基本语句,对应linq我们又了解到lambda表达式,静态扩展方法,以及linq的延迟加载的特性,那么在本篇文章中我们将分享学习一下linq对于我们开发中常用到的对象的操作应用。如果没有阅读过上篇的请点击这里,如果没有阅读中篇的请点击这里 linq to DataSet对于做.net 开发的有谁不知道DataSet,DataTable,DataRow,DataColumn这些对象,如果你真的不知道,那好吧建议你到菜市场买2块豆腐撞死算了>_<。也许你会惊讶,哇靠!linq能操作这些?答案是肯定的。那么我们来看看linq是怎么操作的。 1. 命名空间,如果需要linq操作DataSet,需要以下命名空间using System.Data; using System.Linq; 2. 关键方法 AsEnumerable,该方法为一个静态扩展方法,他将DataTable转换为一个IEnumerable<DataRow>的序列var dt = new DataTable(); dt.Columns.Add("A", typeof(int)); var newRow1 = dt.NewRow(); var newRow2 = dt.NewRow(); newRow1["A"] = 1; newRow2["A"] = 2; dt.Rows.Add(newRow1); dt.Rows.Add(newRow2); // 重点看这里 IEnumerable<DataRow> rows = dt.AsEnumerbale(); foreach(row in rows) Console.WriteLine(row["A"].ToString()); 从这段代码看,并没有什么实质意义,如果这样去遍历datarow就是脱裤子放屁,多此一举。但是这样做的目只有一个为下面的linq操作做铺垫。 3. linq 对 DataRow 操作,以下举例一些linq特有的常用datarow操作Distinct,顾名思义该方法返回的没有重复值的datarow序列 var dt = new DataTable(); dt.Columns.Add("A", typeof(int)); var newRow1 = dt.NewRow(); var newRow2 = dt.NewRow(); newRow1["A"] = 1; newRow2["A"] = 2; newRow3["A"] = 2; dt.Rows.Add(newRow1); dt.Rows.Add(newRow2); dt.Rows.Add(newRow3); // 重点看这里 IEnumerable<DataRow> rows = dt.AsEnumerbale(); 注意,这里的 DataRowComparer 是一个静态类,属性 Default 表示返回单一的DataRow实例 Except, 找到DataRow序列A中在datarow序列B中没有的datarow序列 var dt1 = new DataTable(); dt1.Columns.Add("A", typeof(int)); var dt2 = new DataTable(); dt2.Columns.Add("A", typeof(int)); dt1.Rows.Add(new object[] { 1 }); dt1.Rows.Add(new object[] { 2 }); dt2.Rows.Add(new object[] { 2 }); dt2.Rows.Add(new object[] { 3 }); // 重点看这里 IEnumerable<DataRow> rows1 = dt1.AsEnumerable(); IEnumerable<DataRow> rows2 = dt2.AsEnumerable(); // 获取rows1中在rows2里没有包含的datarow序列 var rows3 = rows1.Except(rows2, DataRowComparer.Default); foreach (var row in rows3) Console.WriteLine(row["A"].ToString());
Intersect, 两个DataRow序列的交集 var dt1 = new DataTable(); dt1.Columns.Add("A", typeof(int)); var dt2 = new DataTable(); dt2.Columns.Add("A", typeof(int)); dt1.Rows.Add(new object[]{1}); dt1.Rows.Add(new object[]{2}); dt2.Rows.Add(new object[]{2}); dt2.Rows.Add(new object[]{3}); // 重点看这里 IEnumerable<DataRow> rows1 = dt1.AsEnumerbale(); IEnumerable<DataRow> rows2 = dt2.AsEnumerbale(); // 获取rows1与rows2共有的datarow序列 rows1.Intersect(row2, DataRowComparer.Default); foreach(var row in rows1) Console.WriteLine(row["A"].ToString()); // 结果 // 2 Union,合并两个datarow序列 var dt1 = new DataTable(); dt1.Columns.Add("A", typeof(int)); var dt2 = new DataTable(); dt2.Columns.Add("A", typeof(int)); dt1.Rows.Add(new object[] { 1 }); dt1.Rows.Add(new object[] { 2 }); dt2.Rows.Add(new object[] { 2 }); dt2.Rows.Add(new object[] { 3 }); // 重点看这里 IEnumerable<DataRow> rows1 = dt1.AsEnumerable(); IEnumerable<DataRow> rows2 = dt2.AsEnumerable(); // 合并rows1与rows2 var row3 = rows1.Union(rows2, DataRowComparer.Default); foreach (var row in row3) Console.WriteLine(row["A"].ToString()); // 结果 // 1 // 2 // 3
SequenceEqual,判断两个dataorw序列是否相等 var dt1 = new DataTable(); dt1.Columns.Add("A", typeof(int)); var dt2 = new DataTable(); dt2.Columns.Add("A", typeof(int)); dt1.Rows.Add(new object[]{1}); dt1.Rows.Add(new object[]{2}); dt2.Rows.Add(new object[]{2}); dt2.Rows.Add(new object[]{3}); // 重点看这里 IEnumerable<DataRow> rows1 = dt1.AsEnumerbale(); IEnumerable<DataRow> rows2 = dt2.AsEnumerbale(); // 合并rows1与rows2 var equal = rows1.SequenceEqual(row2, DataRowComparer.Default); Console.WriteLine(equal.ToString()); // 结果 // false 4. linq 对 DataColumn 操作在了解了对datarow的操作后,我们再来了解一下对datacolumn的操作 Field<T> , 它是一个获取当前行(datarow)的某一列值的静态扩展方法,它具有三种重载参数,类型分别是DataColumn, String, Int,在这里建议大家使用string 类型参数,明确是取哪一列,语句阅读上更流畅。 var dt = new DataTable(); dt.Columns.Add("A", typeof(int)); var newRow1 = dt.NewRow(); var newRow2 = dt.NewRow(); newRow1["A"] = 1; newRow2["A"] = 2; dt.Rows.Add(newRow1); dt.Rows.Add(newRow2); IEnumerable<DataRow> rows = dt.AsEnumerbale(); // 重点看这里 foreach(var val in rows.Select(e => e.Field<int>("A")) Console.WriteLine(val.ToString()); SetField<T> , 该方法刚好是对当前行某一列进行赋值操作,同样也具有三种重载参数DataColumn, String, Int,在这里建议大家使用string 类型参数,明确是取哪一列,语句阅读上更流畅。 var dt = new DataTable(); dt.Columns.Add("A", typeof(int)); var newRow1 = dt.NewRow(); var newRow2 = dt.NewRow(); newRow1["A"] = 1; newRow2["A"] = 2; dt.Rows.Add(newRow1); dt.Rows.Add(newRow2); IEnumerable<DataRow> rows = dt.AsEnumerbale(); // 重点看这里 foreach(var row in rows) row.SetField<int>("A", row.Field<int>("A")+10); foreach(var val in rows.Select(e => e.Field<int>("a"))) Console.WriteLine(val.ToString()) // 结果 // 11 // 12 5. CopyToDataTable<DataRow>,该方法是将datarow序列组成一个新的DataTable// 已知一个DataTable var dt = new DataTable(); // 获取一个DataRow序列 var rows = dt.AsEnumerable(); //经过一系列操作 // .... //获取一个新的DataTable var newDt = rows.CopyToDataTable(); 至此,我们对linq to DataSet 有了一个基本认识与了解,那么下面我们将了解另一个应用 linq to xml
linq to XML在实际应用中,并不需要我们使用linq对xml对象进行操作,因为MS已经提供了封装对xml的linq操作的对象,我们一起来简单的了解下有哪些对象。 1.命名空间,linq to xml 需要如下命名空间using System.Linq; using System.Xml.Linq; 2. linq to xml 主要类型对象XDocument : 表示 XML 文档 XElement : 表示一个 XML 元素 XAttribute : 表示一个 XML 特性(节点属性) XNamespace : 表示一个 XML 命名空间 XCData : 表示一个包含 CDATA 的文本节点(注释) XDeclaration : 表示一个 XML 声明 // 创建一个XML文档 var xDoc = new XDocument( // 定义声明 new XDeclaration("1.0", "utf-8", "yes"), // 添加根节点 new XElement("root", // 添加子节点1,并添加节点属性“name” new XElement("item1", new XAttribute("name","属性"), "子节点1"), // 添加子节点2,并添加内容注释CDATA new XElement("item2", new XCData("注释")))); // 输出结果 //<?xml version="1.0" encoding="utf-8" standalone="yes"?> //<root> // <item1 name="属性">子节点1</item1> // <item2><![CDATA[注释]]></item2> //</root> 2. 输出XML文档,当我们创建好一个xml文档对象时,调用该对象的方法 Save 即可,如下:// 创建一个XML文档 var xDoc = new XDocument( // 定义声明 new XDeclaration("1.0", "utf-8", "yes"), // 添加根节点 new XElement("root", // 添加子节点1,并添加节点属性“name” new XElement("item1", new XAttribute("name","属性"), "子节点1"), // 添加子节点2,并添加内容注释CDATA new XElement("item2", new XCData("注释")))); 3. 导入xml文档,如果已知一个XML文本文件,我们需要获取这个xml文本文件XDocment对象时,可以执行改对象方法 Load,该方法具有八种参数重载,参数类型分别是Stream,String,TextReader,XmlReader。下面的示例中使用的是 string 类型参数传递XDocment xDoc = XDocument.Load("demo.xml"); 4. XNode 抽象基类,表示 XML 树中节点的抽象概念(元素、注释、文档类型、处理指令或文本节点),简单理解就是我们可以把XML的内容每一个部分都视为节点,也就是说它是类型的基类,并提供了大量的操作xml方法。摘自MSDN:
5. 常用遍历方法 DescendantNodes : 按文档顺序返回此文档或元素的子代节点集合。 Elements : 按文档顺序返回此元素或文档的子元素集合 var xDoc = XDocument.Load("demo.xml"); IEnumerable<XNode> nodex = xDoc.DescendantNodes(); IEnumerable<XElement> elems = xDoc.Elements(); 当我们获取到到节点或者元素的序列后就可以对这些对象进行常规的LINQ操作,例如运用前两篇介绍的知识。 由于篇幅关系,这里就不逐一去讲解每个LINQ TO XML的API了,感兴趣的读者可以去msdn查阅System.Xml.Linq命名空间下的操作对象。
总 结(吐槽)当我写完这篇文章时,自我感觉很枯燥,通篇介绍的都是API封装没有体现出LINQ的新意,想删掉这篇文章的冲动都有,但是一想既然我们要学习LINQ,这些东西还是需要适当了解与接触,所以还是硬着头皮写下来了,如果你能看完整篇文章,那真的非常感谢,感谢你的支持。 linq to xml 在效率上和 xml 的 xpath 差不了多少,所以在什么情况下怎么使用任君选择,并不需要强制使用的。 linq to dataset 小数据的时候可以这么干,但是数据量大时候,我建议不要这么干,首先要执行AsEnumberable这样一个耗时的方法划不来,不如直接上foreach遍历。 最终篇将和大家分享并讨论最受大家所熟知的LINQ TO SQL,还是希望大家给予一点期待吧。 感谢阅读,如果有说的不对的地方请指正,谢谢。 |
firefly
天意无私
|
到现在为止你还未触碰LINQ,那进来吧 —— LINQ入门(完结篇)前 言各种懒惰,各种拖沓,终究是要动笔写终结篇了,在这个系列的前几篇文章里我们主要学习linq的基础语法以及他对内存数据的操作等,那么本篇文章我们将讨论学习最为大家所熟悉的,也是最受争议的 Linq To SQL,再次强调,如果你到目前为止认为LinqToSql就是linq的话,有以下几种方式可共君选择:1.把这个系列的前面几篇文章给读了。2.到菜市场卖块豆腐给撞了。3.(MM可以忽略跳过哈)把屁股洗干净,让大家把你菊花给爆了。 用 意Linq To Sql 相对现在来说,不可否认它已经过时了,伴随着vs2010和Entity Fromwork 4的出现,linq to sql 退出历史舞台是必然的,因为EF4比之更强大更完善。但是linq to sql 并不是一无是处,有很多东西它与EF4是相通的,简单的了解linq to sql并无害处,并且还可以对EF4有一定的过渡帮助。 由于这个主题能讲的内容非常多,篇幅关系不能全部说完,在这里只能简单地向大家分享个大概,敬请谅解。
目 录
什么是Linq To Sql摘自MSDN:LINQ to SQL 是 .NET Framework 3.5 版的一个组件,提供了用于将关系数据作为对象管理的运行时基础结构。 在 LINQ to SQL 中,关系数据库的数据模型映射到用开发人员所用的编程语言表示的对象模型。 当应用程序运行时,LINQ to SQL 会将对象模型中的语言集成查询转换为 SQL,然后将它们发送到数据库进行执行。当数据库返回结果时,LINQ to SQL 会将它们转换回您可以用您自己的编程语言处理的对象。简单的理解就是我们对数据进行实体化操作,例如我们可以吧每章表作为一个数据实体封装操作。 生成实体在linq to sql中,实体对象时一个非常重要的环节,他是对数据表,视图等对象的映射,没有实体就谈不上linq to sql了。也许有些老手会反对为什么是生成实体而不是手写实体,生成实体会产生冗余代码。个人认为对于初学者来说,我们很多手头上的项目通常总是先有库表后有代码,那么我们会针对库表进行编写实体,这真的是个体力活没有任何捷径可言,一张一张表的写实体非常痛苦。所以干脆让大家直接生成,即省事又方便,而且也可以学到怎样编写比较专业的实体。 既然是生成实体,那么这肯定需要一些而外的工具了,在这里MS自VS2008起就给我们提供了这么一个工具SqlMetal.exe命令工具,可为 LINQ to SQL 组件生成代码和映射。那么接下来我们演示如何生成实体 1.假设我们有一张用户表,如图:
2.打开VS2008命令行工具,如图:
3.输入命令,生成数据实体。注意:生成实体文件分别有2种,一种是*.cs文件和*.xml映射文件的组合方式,另一种则是*.dbml文件,只能二选一,切记!! 首先我们先生成第一种:*.cs和*.xml组合方式,如图:
4.根据命令指定的位置,我们可以看到对应的生成文件,如图:
5.将生成的文件放入我们的项目中,如图:
注意,在这里我们要选中“linqToSqlMap.xml”,在属性对话框里设置始终复制到输出目录里,如图:
6.接下来,我们看看类文件,生成了那些实体代码,如图:
xml 映射配置文件
从上图看,生成的代码貌似有点多,但是这要比我们自己手写代码更专业。实体文件主要分为两部分,一是数据库上下文关联类 LinqToSqlDemo,二是对应表的实体类 Users 。到这里我们对第一种组合方式的实体生成就已完成。 接下来我们看看要是我们使用的是生成*.dbml文件又会什么样的场景呢。 1.同上,输入命令生成文件,如图:
2.查看生成文件,如图:
3.将生成的DBML文件放入项目里,我们可以看到,生成的只有一个文件,但是当添加到项目里时,项目会自动生成layout和designer两个文件,如图:
4.有意思东西来了,右键点击dbml文件,选择视图设计器,我们可以在编辑框中得到实体映射编辑视图
5.我们看一下这个时候在*.designer.cs文件里生成了哪些内容
可以看到,生成的实体文件和上一种方式生成的实体文件区别不大,由于没有了XML映射配置,所以这里采用的是特性映射配置,在Users实体类中我们可以看到附加了一些如Table,Column的特性标记。 到此我们对实体生成的做法有了一个基本的认识,接下来我们看看linq 是怎么通过实体进行增删改查的 LinqToSql 增删改查1.DataContext 实例既然要对数据进行CURD,那么我们就需通过对数据库上下文关联类的实例进行操作之,即DataContext派生类,如上面生成的LinqToSqlDemo派生类。 由于DataContext 具有多个重载构造函数,在这里针对先前的2种实体生成方式对具体的2个构造函数进行描述,其他的就不逐个介绍少了。详情可以查阅MSDN 如果使用的是*.cs 与 *.xml组合方式的实体映射,那么在构造实例应该如下
如果使用的是*.dbml文件的实体映射,那么就简单了,直接提供数据库连接字符串就可以,因为在派生类的内部已经指定使用特性映射配置。见下图
2.查询数据// 假设我们已构造了DataContext对象实例dataContext // 属性log是实例内部的操作日志输出,它属于Stream类型 输出结果:
如果我们带上where 条件,查询的操作又是如何的呢 // 假设我们已构造了DataContext对象实例dataContext // 属性log是实例内部的操作日志输出,它属于Stream类型 dataContext.Log = Console.Out; var users = from usr in dataContext.Users where usr.UserName == "张三" select usr; foreach (var usr in users) { Console.WriteLine("用户名:{0},Email:{1}", usr.UserName, usr.Email); } Console.Read(); 输出结果:(这里我们可以看到sql使用了参数化查询)
3.关联查询往往在实际项目中我们会涉及到几个表的关联查询,那么LinqToSql有时怎样支持的呢。 假设多了一张用户详细表,他与用户表的关系如下:
生成实体(*.dbml):
在生成的实体代码*.designer.cs文件中我们会看到,Users 实体类多了一个EntityRef<UserDetails> _UserDetails私有字段,而在UserDetails实体类中对了一个EntityRef<Users> _Users私有字段,泛型类EntityRef<T>是关键,他是实体之间关联关系处理主要对象。篇幅关系详情请查阅MSDN点击这里 查询: // 假设我们已构造了DataContext对象实例dataContext // 属性log是实例内部的操作日志输出,它属于Stream类型 dataContext.Log = Console.Out; var users = from usr in dataContext.Users select usr; foreach (var usr in users) { Console.WriteLine("用户名:{0},Email:{1},年龄{2},住址:{3}", usr.UserName, usr.Email, usr.UserDetails.Age, usr.UserDetails.Address); } Console.Read(); 输出结果:
从结果我们可以看到,首先是把用户表给查了,然后根据linq延迟加载的特性,只有真正使用时才执行,因此当需要查看用户详细信息时才会去执行用户详细查询,这样就带来了很大弊端,如果数据量大时那么这样的查询开销就大了,大大降低了程序的效率。那么这个问题是否可以解决呢?答案是肯定的,请看下面代码: // DataLoadOption数据导入操作对象,它可以告诉linq在执行查询是否延迟 // 查询对象的子对象 var loadOption = new DataLoadOptions(); // 设置数据导入对象关联关系 loadOption.LoadWith<Users>(usr => usr.UserDetails); dataContext.LoadOptions = loadOption; var users = from usr in dataContext.Usersselect usr; foreach (var usr in users) { Console.WriteLine("用户名:{0},Email:{1},年龄:{2},住址:{3}", usr.UserName, usr.Email, usr.UserDetails.Age, usr.UserDetails.Address); } Console.Read(); 输出结果:
从log我们可以看到这样就是一条语句查出来数据结果集。注意,这里演示的是2表关系的查询,如果当我们再多出一个表,而这表是与用户详细表形成关联关系的时候那么,DataLoadoption 就没法解决了一次性查出,而又回到类似上一个样例分次查出来。例如: 假设新增一张表(UserDetails2): 关系如下:
生成*.dbml:
查询: // 假设我们已构造了DataContext对象实例dataContext // 属性log是实例内部的操作日志输出,它属于Stream类型 dataContext.Log = Console.Out; // DataLoadOption数据导入操作对象,它可以告诉linq在执行查询是否延迟 // 查询对象的子对象 var loadOption = new DataLoadOptions(); // 设置数据导入对象关联关系 loadOption.LoadWith<Users>(usr => usr.UserDetails); 查询结果:
这里我们可以看到,在第一个层级使用了一次查询,但是往下一个层级查询UserDetails2表时又变回了分开查询,效率就大打折扣了。 4.数据新增通过执行Table<TEneity>类的方法 InsertOnSubmit 新增数据 var dataContext = new LinqToSqlDemo(CONN_STR); // 新增用户 dataContext.Users.InsertOnSubmit( new Users { UserId = 4, UserName = "赵六", Email = "wangw@xxx.com", UserDetails = new UserDetails { Address = "xxx大道", Age = 25, UserId = 4 } }); // 注意,这里是必须的,提交修改 dataContext.SubmitChanges(); 5.数据删除通过执行Table<TEntity>类的方法 DeleteOnSubmit 进行删除数据 var dataContext = new LinqToSqlDemo(CONN_STR); var user = (from usr in dataContext.Users where usr.UserName == "张三" select usr).SingleOrDefault(); dataContext.Users.Attach(user); // 上传数据 dataContext.Users.DeleteOnSubmit(user); 6.数据更新在对实体更新完毕后直接调用DataContext的SubmitChanges方法即可。 var dataContext = new LinqToSqlDemo(CONN_STR); var user = (from usr in dataContext.Users where usr.UserName == "张三" select usr).SingleOrDefault(); user.UserName = "张三三"; dataContext.Users.Attach(user, false); // 更新数据 dataContext.SubmitChanges(); 注意,在数据删除和更新的时候我们可以看到都执行了Table<TEntity>类的Attach方法,它是允许反序列化的实体与 DataContext 的新实例关联,以便可以从数据库更新或删除这些实体,篇幅关系,详情可以查阅MSDN。 拓 展通过上面我们对LinqToSql有了一个最基本的知识,由于篇幅关系,没法在一篇文章里逐一详谈,下面是一些进阶内容,希望对大家有所帮助 Linq To Sql 白皮书(英文版,非常详细,想在这方面有深入研究的就花一点时间啃吧) 优缺杂谈总的来说linq To sql 还是不错的,把讨厌的sql命令从我们项目里移除出去,从实体出发,更贴近OOP。不过有时候生成的代码虽然专业,但是冗余的还是有不少,同时对linq To Sql 生成的SQL语句确实不太感冒效率并不是很高,在查询时还会带上不需要的字段,因此如果是小项目快速开发还行,大项目还是请各位看官多斟酌斟酌吧。 总 结到这,关于linq的这个系列算是结束了,通过这个系列大家应该对linq有了一个最基本的认识,因此光光掌握这个系列所结束的知识在实战应用中是不够的,需要各位看官在往后的自我学习中去查阅更多的相关知识做更多的实践,园子里对linq的介绍文章有很多,大家都不妨去阅读一下。正所谓师傅领进门,修行靠个人,希望大家都学习linq,掌握linq,个个都是linq高手,以便鄙人向大家学习,三人行必有我师焉。最后谢谢您的阅读,有说得不对之处请指正,谢谢。 |