介绍LINQ
一,什么是LINQ?
LINQ最初的动机是除理运用数据库与.NET编程语言时所遇到的概念与技术上的问题,微软的意图是为对象-关系的映射问题提供一个解决方案,同时简化对象与数据源之间的相互作用. LINQ最终演化成一个综合语言的查询工具,这种工具可以从记忆性对象( LINQ to Object),数据库(LINQ to Sql),XML文档( LINQ to XML),文件系统或其它数据源中获得数据.
二概述
LINQ也许是数据世界与通用编程语言中缺失的一环, LINQ 统一了数据的获取,而无论它是什么样的数据源,允许它从不同的数据源获得混合数据,它允许查询和设置属性,类似于数据库提供的SQL.LINQ可以用.Net语言像C#,VB通过这些语言的扩展直接进行综合查询,LINQ的意思就是Language-INtegrated Query.
LINQ是建立在一个统一的基础上,像查询表达式,查询操作符与表达式树,这使得LINQ做为一种工具更具扩展性.
• 一,LINQ to Sql通过LINQ扩展来进行综合语言的数据获取,它建立在ADO.NET对象映射数据表与记录到类和对象.
• ,LINQ to Sql利用了包含在.NET与XML文档中的映射属性,这些信息被用来自动除理留存在关系型数据库中的对象,一个表格可以被映射为一个类,表格中的记录则被映射为类的属性,表格间的关系则被描述为额外的属性.
• LINQ to SQL通过动态的SQL查询或储存的程序可以自动跟踪对象的改变或数据库的更新.
Entity classes
• 建立LINQ to SQL应用程序的第一步是声明要描述应用程序数据的实体类.
• [Table(Name="Contacts")]
• class Contact
• {
• public int ContactID;
• public string Name;
• public string City;
• }
• 表格属性由LINQ to SQL在System.Data.Linq.Mapping
• 命名空间中提供.它由Name属性来指定数据库中特定的表.
• 为了把实体类与表格联系起来,我们还需要指示出与表格中每一列相联系的字段或属性,这被称为列属性.
• [Table(Name="Contacts")]
• class Contact
• {
• [Column(IsPrimaryKey=true)]
• public int ContactID { get; set; }
• [Column(Name="ContactName"]
• public string Name { get; set; }
• [Column]
• public string City { get; set; }
• }
• 列属性也是System.Data.Linq.Mapping命名空间的一部分,它有很多属性使我们可以用来在字段或属性与数据库列之间定制精确的映射,你可以看到我们用IsPrimaryKey
• 来告诉LINQ to SQL表格中名为ContactID
• 的列是表格中的主键, ContactName
• 列被映射为Name字段,我们并不用详细指定其它列的名称与类型, LINQ to SQL可以从类的字段中推断出来.
The DataContext
• DataContext的目的是把面向对象的请求转变为面向数据库的SQL查询语句,并且从这些结果中组合出对象.
• string path = Path.GetFullPath(@"../../../../Data/northwnd.mdf");
• DataContext db = new DataContext(path);
• DataContext构造器类把一个连接字符串做为参数,
• DataContext类可以使用数据库中的表格,
• Table<Contact> contacts = db.GetTable<Contact>();
• DataContext.GetTable是一个通用的方法,它可以使我们使用强类型的对象,
• 让我们总结下LINQ to SQL自动为我们完成的功能:
• 1,打开到数据库的连接;
• 2,自动生成SQL查询;
• 3,自动执行面向数据库的SQL查询,
• 4,从结果中填充与创建我们的对象;
C# and VB.NET语言提高
• 泛型 (Generics), 匿名方法(anonymous methods)与迭代器(iterators)是LINQ的关键组件.
• 隐式类型的局部变量:它允许局部变量的类型可以从初始化它的表达式推导出来;
• 对象初始化:更容易的构建与初始化对象;
• 拉姆达表达式,一个匿名方法的演变,提供改善的类型推理和转换成委托类型与表达式树.
• 可扩展的方法:可以用额外的方法扩展现有的类型与构造的类型.
• 匿名类型:对象初始化时自动创建与推断类型.
小小知识点
• 泛型第一次出现.NET2.0中,它允许我们最大限度的实现代码的重用,类型安全与执行,最常被我们用做创建强类型的集合类.
2.2隐式类型的局部变量
• 当我们用关键字斟句酌Var声明一个局部变量时,编译器会根据我们初始化它的表达式推断这个变量的类型.
• 句法:
• var i = 12;
• var s = "Hello";
• var d = 1.0;
• var numbers = new[] {1, 2, 3};
• var process = new ProcessData();
• var processes =
• new Dictionary<int, ProcessData>();
2.3对象与集合的初始化
• 对象初始化允许我们指定一个对象的一个或多个字段或属性的值
• 注意:这只对容易获得的字段或属性有效,等号后表达式的除理方式与指定字段或属性的除理方式相同;
• int i = 12;
• string s = "abc"
• string[] names = new string[] {"LINQ", "In", "Action"}
• In C#
• var data = new ProcessData {Id = 123, Name = "MyProcess",
• Memory = 123456};
2.3.2集合的初始化
这种句法允许我们初始化不同类型的集合, 提供他们执行,System.Collections.IEnumerable与提供合适的Add方法.
var digits = new List<int> {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
这等价于下面的代码,它是被编译器自动生成的.
• List<int> digits = new List<int>();
• digits.Add(0);
• digits.Add(1);
• digits.Add(2);
• ...
• digits.Add(9);
• 集合初始化例子:
• var processes = new List<ProcessData> {
• new ProcessData {Id=123, Name="devenv"},
• new ProcessData {Id=456, Name="firefox"}
• }
• new Dictionary<int, string> {{1, "one"}, {2, "two"}, {3, "three"}}
• 对象初始化的优点:
• 1,我们可以用一个指令初始化一个对象;
• 2,我们并不需要为一个简单的对象提供一个构造器;
2.4 Lambda表达式
• address => address.City == "Paris"
• 为了使我们的代码更通用,我们需要把一个过滤的信息做为我们方法的参数,而不是硬编码,在C#2.0或以前更早的版本中我们用的是委托,委托存储了一个指向一个方法的指针.
• delegate Boolean FilterDelegate(Process process);
• 返回一个Boolean值指示参数是否匹配某个标准;
• 如果不创建自己的泛型类我们还可以使用.Net2.0提供的Predicate<T> 类型.
• delegate Boolean Predicate<T>(T obj);
• 这个Predicate<T>委托类型代表了一个方法返回一个true or false基于它的输入.
2.4.2匿名方法
• 委托存在于C#1.0,但在C#2.0中被改善为运用匿名方法来实现委托.
• 由于匿名方法,我们不用再声明一个过滤器似的方法,我们可以直接传递代码.
• 注意:Vb.Net不支持匿名方法/
• 在System.Collections.Generic.List<T> and System.Array中.NET2.0介绍了许多方法被用于匿名方法.
• 2.4.3介绍 lambda表达式
• 在C#3.0中我们用lambda表达式来代替匿名方法.
• lambda表达式优点:
• 1, lambda表达式可自动推测参数类型,所以允许我们忽略不写了;
• 2, lambda表达式即可以是语句块也可以是表达式,比匿名方法提供了更简明的句法,匿名方法中只能是语句块.
• 3, lambda表达式可以参于参数类型的推断与方法的重载,匿名方法只能参于参数的推断.
• 4, lambda表达式可以转换表达式体与表达式树.
• 在C#中lambda表达式被写为一个参数列表在=>后,跟一个表达式或语句块.
•
•
• 操作符左边是输入参数,右边是表达式或语句块(包括在一个括号中).
• 1,x => x + 1
• 2,x => { return x + 1; }
• 3,(int x) => x + 1
• 4,(int x) => { return x + 1; }
• 5,(x, y) => x * y
• 6,() => 1
• 7,() => Console.WriteLine()
• customer => customer.Name
• person => person.City == "Paris"
• (person, minAge) => person.Age >= minAge
• 1,Implicitly typed, expression body
• 2,Implicitly typed, statement body
• 3,Explicitly typed, expression body
• 4,Explicitly typed, statement body
• 5,Multiple parameters
• 6,No parameters, expression body
• 7,No parameters, statement body
• 1,Func<DateTime> getDateTime = () => DateTime.Now;
• 2,Action<string> printImplicit = s => Console.WriteLine(s);
• 3,Action<string> printExplicit = (string s) => Console.WriteLine(s);
• 4,Func<int, int, int> sumInts = (x, y) => x + y;
• 4,Predicate<int> equalsOne1 = x => x == 1;
• Func<int, bool> equalsOne2 = x => x == 1;
• 6,Func<int, int> incInt = x => x + 1;
• Func<int, double> incIntAsDouble = x => x + 1;
• 7,Func<int, int, int> comparer = (int x, int y) =>
• {
• if (x > y) return 1;
• if (x < y) return -1;
• return 0;
• };
• 1,No parameter
• 2,Implicitly typed string parameter
• 3,Explicitly typed string parameter
• 4,Two implicitly typed parameters
• 5,Equivalent but not compatible
• 6,Same lambda expression but different delegate types
• 7,Statement body and explicitly typed parameters
• 2.5扩展方法
• 为了把我们现有的方法转变为扩展方法,我们只需要把This关键字做为第一个参数.
• static Int64 TotalMemory(this IEnumerable<ProcessData> processes)
• {
• Int64 result = 0;
• foreach (var process in processes)
• result += process.Memory;
• return result;
• }
• This关键字指示编译器把这个方法做为一个扩展方法.它指示This是一个方法可扩展IEnumerable<ProcessData>类型的对象
• 注意:
• 在C#中,扩展方法只能声明在非泛型的静态类中,扩展方法可带有任意数量的参数但第一个必须是this.
• 警告:
• 扩展方法具有较低的优先权,不能隐藏一个实例方法.只有当没有相同签名的方法存在时,扩展方法才被调用.
• 2.6匿名类型
• var contact = new { Name = "Bob", Age = 8 }
• 可以利用匿名类型把数据分组为一个对象.
• LINQ building blocks
•
•
• 3.2介绍序列
• var processes =
• Process.GetProcesses() (1)
• .Where(process => process.WorkingSet64 > 20*1024*1024) (2)
• .OrderByDescending(process => process.WorkingSet64) (3)
• .Select(process => new { process.Id, (4)
• Name=process.ProcessName });
• (1)Get a list of running processes
• (2)Filter the list
• (3).Sort the list
• (4)Keep only the IDs and names
• 3.2.1 IEnumerable<T>
• System.Diagnostics.Process类的GetProcesses方法返回了一个Process
• 对象的数组,数组执行了泛型IEnumerable<T>的接口.这个接口出现在.NET2.0中,LINQ中也适用,在我们的例子中进程对象的数组执行了IEnumerable<Process>.这个IEnumerable<T>接口很重要.因为像上述的1,2,3,4,还有其它的一些查询操作以及其它一些类型的对象都是做为参数.
• public static IEnumerable<TSource> Where<TSource>(
• this IEnumerable<TSource> source,
• Func<TSource, Boolean> predicate)
• {
• foreach (TSource element in source)
• {
• if (predicate(element))
• yield return element;
• }
• }
• Where是一个扩展方法.这些扩展方法(Where,
• OrderByDescending, and Select)由System.Linq.Enumerable类提供, 这个类的名字来自于这样一个事实,扩展方法控制着
• IEnumerable<T>对象的工作.
• 注意:sequence标明所有的都执行IEnumerable<T>.
• 3.2.2迭代器
• 一个迭代器是一个对象允许你查阅一个集合的元素.每个.NET集合(例如:List<T>, Dictionary<T>, and ArrayList)都有一个GetEnumerator方法,通过对其内容的迭代返回一个对象.
• 一个迭代器很像传统的方法返回一个集合,因为它产生了一个序列值,例如我们可以创建这样一个方法来返回一个整形的enumeration
• int[] OneTwoThree()
• {
• return new [] {1, 2, 3};
• }
• 但是C# 2.0 or 3.0中的迭代器是不同的,不是建立一个值的集合一块返回它,而是一次返回一个值,这需要更少的内存,而且可以使调用者立刻除理第一个值而不是等整个集合准备好后.
• 3.2.3延迟的查询执行
• var query =
• from n in numbers
• select Square(n);
• 在编译时转为方法的调用, 一旦编译, 查询变为IEnumerable<double> query =
• Enumerable.Select<int, double>(numbers, n => Square(n));
• Enumerable.Select方法是个迭代器,这解释了为什么延迟执行.
• 我们要明白查询变量并不是描述了查询的结果,只是说潜在的有个可执行的查询,这个查询在分给一个变量时并没有执行.
• 延迟查询的另一个优点是保存了资源,如果我们只是想看下结果而不除理,这些结果则不会保存在内存中,因为这些结果作为一个序列被提供而不是保存在数组或列表中.
• Reusing a query to get different results
• 如果你第二次重新声明一个查询的结果则它会产生一个不同的值,
• using System;
• using System.Linq;
• static class QueryReuse
• {
• static double Square(double n)
• {
• Console.WriteLine("Computing Square("+
• return Math.Pow(n, 2);
• }
• public static void Main()
• {
• int[] numbers = {1, 2, 3};
• var query =
• from n in numbers
• select Square(n);
• foreach (var n in query)
• Console.WriteLine(n);
• for (int i = 0; i < numbers.Length; i++)
• numbers[i] = numbers[i]+10;
• Console.WriteLine("- Collection updated -");
• foreach (var n in query)
• Console.WriteLine(n);
• }
• }
• Computing Square(1)...
• 1
• Computing Square(2)...
• 4
• Computing Square(3)...
• 9
• - Collection updated -
• Computing Square(11)...
• 121
• Computing Square(12)...
• 144
• Computing Square(13)...
• 169
• Forcing immediate query execution
• 延迟查询是一种默认的行为,只有当我们从中取得数据时它才执行,如果想立即执行,必须显示请求.加一个System.Linq.Enumerable类的扩展方法,ToList()foreach (var n in query.ToList())
• Console.WriteLine(n);
• ToList()迭代器创建了一个List<double>实例并拥有查询的所有结果. Where, OrderByDescending, and Select方法都是迭代器,这说明当我们枚举出结果之前这些参数调用的这些方法将不会发生.
• 3.3查询操作符
• 查询操作符是.NET Framework类库的一个扩展,是执行在LINQ上下文中的一套可扩展方法,
• 3.3.2查询表达式
3.5介绍表达式树
Querying objects in memory
• 并不是所有的查询都可以通过LINQ to Objects.应用的第一条标准是对象必须的集合, 而且必须执行IEnumerable<T>接口,并不是所有的集合都实现这个接口,只有强类型的对象可以,如:Arrays, generic lists, and generic dictionaries
• 非泛型的集合实现的是这个IEnumerable
但你可以用Cast and OfType操作符查询这些非泛型的合
Beyond basicin-memory queries
查询非泛型集合用Cast转换.也可以用OfType,不同的是它只返回某一类型的对象
• 为了识别我们映射的表在那个数据库,我们用了DataContext对象.
• 1,掌管着到数据库的连接,通过传递一个连接数据库的字符串建立一个它的实例;
• DataContext dataContext = new DataContext(liaConnectionString);
• 2,它管理着我们的映射,并提供给我们关键的资源.
• Table<Book> books = dataContext.GetTable<Book>();