LINQ基础教程 LINQ基础教程 主讲人:李春涛 徐卫东
1 3 2 学习目标 LINQ基本概念的掌握 LINQ查询方法及项目中的应用 LINQ进阶 1、基本概念: 理解查询语句与查询方法的关系 掌握各种高级查询方法
1 LINQ基本概念的掌握 设计、组织和协作
LINQ是Language Integrated Query的简称。是 Visual Studio 2008 和 LINQ是Language Integrated Query的简称。是 Visual Studio 2008 和 .NET Framework 3.5 版中一项突破性的创新,它在对象领域和数据领域之间架起了一座桥梁。 微软在2007年,将LINQ作为.NET Framework 3.5的一部分正式发布,编写程序时可以得到很好的编译时语法检查,丰富的元数据,智能感知、静态类型等强类型语言的好处。 Linq目标是实现语言与数据的深度结合,Linq以统一的数据访问方式访问各种数据源,以相同的方式读取数据,象SQL语句一样进行查询,而这些都是整合在我们熟悉的编程语言C#当中,另外Linq是使用强类型,并提供编译时检查和VS的智能感知特性等。 通过使用查询语法,可以使用最少的代码对数据源执行复杂的筛选、排序和分组操作。 举例:分组
LINQ包括五个部分:LINQ to Objects、LINQ to DataSets、LINQ to SQL、LINQ to Entities、LINQ to XML。 LINQ to Objects在当前项目中应用最为频繁,是我们可以简单的对内存中所有基于IEnumerable<T>接口的数据源进行操作。 LINQ to SQL全称基于关系数据的.NET语言集成查询,用于以对象形式管理关系数据,并提供了丰富的查询功能。其建立于公共语言类型系统中的基于SQL的模式定义的集成之上,当保持关系型模型表达能力和对底层存储的直接查询评测的性能时,这个集成在关系型数据之上提供强类型。 LINQ to XML在System.Xml.LINQ命名空间下实现对XML的操作。采用高效、易用、内存中的XML工具在宿主编程语言中提供XPath/XQuery功能等。 LINQ已成为编程语言的一个组成部分,在编写程序时可以得到很好的编译时语法检查,丰富的元数据,智能感知、静态类型等强类型语言的好处。并且它同时还使得查询可以方便地对内存中的信息进行查询而不仅仅只是外部数据源。 LINQ定义了一组标准查询操作符用于在所有基于.NET平台的编程语言中更加直接地声明跨越、过滤和投射操作的统一方式,标准查询操作符允许查询作用于所有基于IEnumerable<T>接口的数据源。
LINQ的组成 不同的部分会应用不同的provider,
与LINQ相关的概念 扩展方法 其实仅仅是语法糖,没有扩展方法,Linq的实现肯定不会再像现在这么优雅。从IL层面解释扩展方法的实现,它最终还是调用静态方法。 匿名方法和Lambda表达式 Lambda表达式将函数式编程风格带进了C#这种命令编程语言中,Lambda表达式可以编译成表达式树。 匿名类型与隐式类型局部变量(var) 如果没有隐式类型局部变量,使用Linq查询的时候不会再像现在这么轻松吧. var KeyPair1 = new { Key="yuyi",Value="Programer"}; 匿名类型在编译阶段编译器就为匿名类型生成了类型的代码,但是生 成的是泛型版本的 对象集合初始化器 编译器会调用无参构造函数,然后为指定的属性赋值。对匿名类型来说是很有用的。 LINQ是在ADO.NET之上的,那么在将来它会代替ADO.NET吗? 在大型项目中使用LINQ,它的效率如何呢? 表达式树是LINQ TO SQL的基础。 1、扩展方法是一种特殊的静态方法(优先级比实力方法底) 2、扩展方法必须在静态类中定义 3、扩展方法的优先级低于同名的类方法 4、不要滥用扩展方法—会造成代码污染? 表达式树就是一种表示表达式的数据结构
// 根据书籍分类获取书籍列表 var booksByCategory = from book in db.Books where book.Category.Name == categoryName select new { Title = book.Title, Author = book.Author, PublisherName = book.Publisher.Name, PublishDate = book.PublishDate, }; var booksByCategoryAndPage = booksByCategory.Skip((pageNo - 1) * PAGE_SIZE).Take(PAGE_SIZE); int pageCount = booksByCategory.Count() / PAGE_SIZE + 1; gvBooks.DataSource = booksByCategoryAndPage; gvBooks.DataBind();
2 LINQ进阶
LINQ查询执行的时机 1、查询分为以下三步:获取数据源、定义查询、执行查询 2、定义查询后,查询直到需要枚举结果时才被真正执行,成为延迟执行;举例 3、当查询方法返回单一值时,查询立即执行。Count()方法 4、可以使用ToList()或ToArray()方法强制立即执行
1、Method Syntax,查询方法方式 LINQ的查询方式 1、Method Syntax,查询方法方式 主要利用System.Linq.Enumberable类中定义的扩展方法和Lambda表达式进行查询。 2、Query Syntax,查询语句方式 一种更接近SQL语法的查询方式,具有更好的可读性。 查询语句与查询方法存在着紧密的关系 CLR本身并不理解查询语句,它只理解查询方法 编译器负责在编译时将查询语句翻译为查询方法 大部分查询方法都有对应的查询语句形式:如 Select() 对应 select 、 OrderBy() 对应 orderby 部分查询方法目前在C#中还没有对应的查询语句:如 Count()和Max() 这时只能采用以下替代方案 查询方法 查询语句 + 查询方法的混合方式; 一般情况下,建议使用可读性更好的查询语句
int[] numbers = new int[] { 6, 4, 3, 2, 9, 1, 7, 8, 5 }; var even = from number in numbers where number % 2 == 0 orderby number descending select number; 查询语句 两者的执行效果完全一样 int[] numbers = new int[] { 6, 4, 3, 2, 9, 1, 7, 8, 5 }; var even = numbers .Where(p => p % 2 == 0) .OrderByDescending(p => p) .Select(p => p); 注意说明大部分查询方法都有对应的查询语句 查询语句与查询方法存在着紧密的关系 CLR本身并不理解查询语句,它只理解查询方法 编译器负责在编译时将查询语句翻译为查询方法 大部分查询方法都有对应的查询语句形式:如 Select() 对应 select 、 OrderBy() 对应 orderby 部分查询方法目前在C#中还没有对应的查询语句:如 Count()和Max() 这时只能采用以下替代方案 查询方法 查询语句 + 查询方法的混合方式; 一般情况下,建议使用可读性更好的查询语句 查询方法
表达式树 1、什么是表达式树? 它是一种抽象语法树或者说它是一种数据结构,通过解析表达式目录树,可以实现我们一些特定的功能,它是LINQ Provider的基础。 我们将Lambda表达式指定给Expression<TDelegate>类型的变量(参数)时,编译器将会发出生成表达式目录树的指令,如上面这段代码中的Lambda表达式(a, b) => a * b + 2将创建一个表达式目录树,它表示的是一种数据结构,即我们把一行代码用数据结构的形式表示了出来,具体来说最终构造出来的表达式目录树形状如下图所示
2、如何构造出表达式目录树 Expression<Func<int, int, int>> expression = (a, b) => a * b + 2; 简单的表达式树,编译器会将表达式用树状的数据结构存储起来
LINQ TO SQL 表达式树 为什么需要表达式树? 当我们在C#中编写一个查询表达式时, 它将返回一个IQueryable类型的值,在该类型中包含了 两个很重要的属性Expression和Provider 查询表达式,将封装为一种抽象的数据结构,这个数据结构就是表达式目录树,当我们在使用上面返回的值时,编译器将会以该值所期望的方式进行翻译,这种方式就是由Expression和Provider来决定。可以看到,这样将会非常的灵活且具有良好的可扩展性,有了表达式目录树,可以自由的编写自己的Provider,去查询我们希望的数据源。经常说LINQ为访问各种不同的数据源提供了一种统一的编程方式,其奥秘就在这里。 INQ to Excel LINQ to Expressions (MetaLinq) LINQ Extender (Toolkit for building LINQ Providers) LINQ to Flickr LINQ to Google LINQ to Indexes (LINQ and i40) LINQ to IQueryable (Matt Warren on Providers) LINQ to JSON LINQ to LDAP LINQ to NHibernate LINQ to JavaScript LINQ to Lucene LINQ to Metaweb(freebase) LINQ to MySQL, Oracle and PostgreSql LINQ to RDF Files LINQ to Sharepoint LINQ to SimpleDB
3 LINQ查询方法及 项目中的应用 一个Linq例子 Linq语法基础 Linq语法的本质 Linq语言要素
一个Linq例子
一个Linq例子 var words = from word in "The quick brown fox jumps over the lazy dog".Split() orderby word.ToUpper() select word;
一个Linq例子 运行结果:
Linq语法基础
Linq语法基础 Where的声明: public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
Linq语法基础 string[] names = { "Tom", "Dick", "Harry" }; IEnumerable<string> filteredNames = System.Linq.Enumerable.Where (names, n => n.Length >= 4); 运行结果:
Linq语法基础 String[] names = { "Tom", "Dick", "Harry" }; IEnumerable<string> result = names.Where(n=>n.Length >=4); 运行结果:
总结 Extension Method是一个定义在Static Class的一个特殊的Static Method。之所以说这个Static Method特别,是因为Extension Method不但能按照Static Method的语法进行调用,还能按照Instance Method的语法进行调用。
Linq语法的本质
Linq语法的本质 string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; IEnumerable<string> query1 = from name in names where name.Contains("a") orderby name.Length select name.ToUpper();
Linq语法的本质 运行结果:
Linq语法的本质 string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; IEnumerable<string> query = names .Where (n => n.Contains ("a")) .OrderBy (n => n.Length) .Select (n => n.ToUpper());
Linq语法的本质 运行结果:
Linq语法的本质 string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; IEnumerable<string> filtered = names.Where (n => n.Contains ("a")); IEnumerable<string> sorted = filtered.OrderBy (n => n.Length); IEnumerable<string> finalQuery = sorted.Select (n => n.ToUpper());
Linq语法的本质 string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; IEnumerable<string> filtered = names.Where (n => n.Contains ("a"));
Linq语法的本质 string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; IEnumerable<string> filtered = names.Where (n => n.Contains ("a")); IEnumerable<string> sorted = filtered.OrderBy (n => n.Length);
Linq语法的本质 运行结果:
Linq语法的本质 string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; IEnumerable<string> filtered = names.Where (n => n.Contains ("a")); IEnumerable<string> sorted = filtered.OrderBy (n => n.Length); IEnumerable<string> finalQuery = sorted.Select (n => n.ToUpper());
Linq语法的本质 运行结果:
Linq的语法,其实就是调用“扩展方法”的语法糖,Linq语句,最终都会转换成对相应的“扩展方法”的调用. 总结 Linq的语法,其实就是调用“扩展方法”的语法糖,Linq语句,最终都会转换成对相应的“扩展方法”的调用.
Linq语言要素
数据结构上的关联 public class Customer { public string Name; public string City; public Order[] Orders; } public class Order{ public int Quantity; public Product product; public class Product{ public string ProductName;
数据结构上的关联 Order[] ordersA = { new Order{Quantity = 10,product= new Product{ProductName = "PA1"}}, new Order{Quantity = 20,product = new Product{ProductName = "PA2"}}, new Order{Quantity =30,product = new Product{ProductName = "PA3"}} }; Order[] ordersB = { new Order{Quantity = 10,product= new Product{ProductName = "PB1"}}, new Order{Quantity = 20,product = new Product{ProductName = "PB2"}}, new Order{Quantity =30,product = new Product{ProductName = "PB3"}} Customer[] Customers = { new Customer{Name= "CA",City= "CityA",Orders =ordersA }, new Customer{Name = "CB",City = "CityB",Orders = ordersB}
var query = from c in Customers from o in c.Orders 数据结构上的关联 查询语句: var query = from c in Customers from o in c.Orders select new { c.Name, o.Quantity, o.product.ProductName }; foreach (var item in query){ Console.WriteLine(item.ToString()); }
{ Name = CA, Quantity = 10, ProductName = PA1 } 数据结构上的关联 运行结果: { Name = CA, Quantity = 10, ProductName = PA1 } { Name = CA, Quantity = 20, ProductName = PA2 } { Name = CA, Quantity = 30, ProductName = PA3 } { Name = CB, Quantity = 10, ProductName = PB1 } { Name = CB, Quantity = 20, ProductName = PB2 } { Name = CB, Quantity = 30, ProductName = PB3 }
数据结构上的关联2 var query = from c in Customers from o in c.Orders where o.Quantity == 20 select new { c.Name, o.Quantity, o.product.ProductName }; foreach (var item in query){ Console.WriteLine(item.ToString()); }
{ Name = CA, Quantity = 20, ProductName = PA2 } 数据结构上的关联2 运行结果: { Name = CA, Quantity = 20, ProductName = PA2 } { Name = CB, Quantity = 20, ProductName = PB2 }
数据结构上的关联3 var query = from c in Customers where c.Name.Contains("CA") from o in c.Orders where o.Quantity == 20 select new { c.Name, o.Quantity, o.product.ProductName }; foreach (var item in query){ Console.WriteLine(item.ToString()); }
{ Name = CA, Quantity = 20, ProductName = PA2 } 数据结构上的关联3 运行结果: { Name = CA, Quantity = 20, ProductName = PA2 }
数据结构上的关联3 var query = from c in Customers from o in c.Orders where c.Name.Contains("CA") where o.Quantity == 20 select new { c.Name, o.Quantity, o.product.ProductName }; where c.Name.Contains("CA") && o.Quantity == 20 以上两种Linq效果相同
public class Customer{ public string Name; public string City; } 数据上的关联 public class Customer{ public string Name; public string City; } public class Supplier{
数据上的关联 Customer[] Customers = { new Customer{City = "CA",Name = "CustA"}, new Customer{City = "CB",Name = "CustB"}, new Customer{City = "CC",Name = "CustC"}, new Customer{City = "CD",Name = "CustD"} }; Supplier[] Suppliers ={ new Supplier{City = "CA" ,Name = "SA"}, new Supplier{City = "CB" ,Name = "SB"}, new Supplier{City = "CC" ,Name = "SC"}, new Supplier{City = "CC" ,Name = "SD"}, new Supplier{City = "CC" ,Name = "SE"}
必须用equals关键字,不能用“==”比较 数据上的关联 必须用equals关键字,不能用“==”比较 var query = from c in Customers join s in Suppliers on c.City equals s.City select new { CCity =c.City, CName = c.Name,SCity= s.City,SName =s.Name }; foreach (var item in query){ Console.WriteLine(item.ToString()); }
数据上的关联 运行结果: { CCity = CA, CName = CustA, SCity = CA, SName = SA } { CCity = CB, CName = CustB, SCity = CB, SName = SB } { CCity = CC, CName = CustC, SCity = CC, SName = SC } { CCity = CC, CName = CustC, SCity = CC, SName = SD } { CCity = CC, CName = CustC, SCity = CC, SName = SE }
数据上的关联2 var query = from c in Customers join s in Suppliers on c.City equals s.City into customerSuppliers select new { CCity = c.City, CName = c.Name, customerSuppliers }; foreach (var item in query){ Console.WriteLine(string.Format("CCity = {0}, CName = {1},", item.CCity, item.CName)); foreach (var sup in item.customerSuppliers) { Console.WriteLine(string.Format("\t->SCity = {0},SName = {1}",sup.City,sup.Name));}}
数据上的关联2 CCity = CA, CName = CustA, ->SCity = CA,SName = SA CCity = CB, CName = CustB, ->SCity = CB,SName = SB CCity = CC, CName = CustC, ->SCity = CC,SName = SC ->SCity = CC,SName = SD ->SCity = CC,SName = SE CCity = CD, CName = CustD,
数据上的关联3 from c in categories join p in products on new { c.IdCategory, c.Year } equals new { p.IdCategory, p.Year } into productsByCategory
int[] numbers = { 10, 9, 8, 7, 6 }; numbers.Take (3)
int[] numbers = { 10, 9, 8, 7, 6 }; numbers.Skip (3)
int[] numbers = { 10, 9, 8, 7, 6 }; numbers.Reverse()
First,Last int[] numbers = { 10, 9, 8, 7, 6 }; numbers.First(); 运行结果: numbers.Last();
int[] numbers = { 10, 9, 8, 7, 6 }; numbers.Count()
int[] numbers = { 10, 9, 8, 7, 6 }; numbers.Min()
Any int[] numbers = { 10, 9, 8, 7, 6 }; numbers.Any();//如果有元素即为true,否则为false numbers.Any (n => n % 2 == 1);
int[] numbers = { 10, 9, 8, 7, 6 }; numbers.Contains (9);
Contains var orderOfProductOne = new Order {IdOrder = 1, Quantity = 3, IdProduct = 1 , Shipped = false, Month = "January"}; bool result = Orders.Contains(orderOfProductOne); 需要覆盖GetHashCode和Equals方法 改Order类为值类型 public static bool Contains<TSource>( this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer )
int[] seq1 = { 1, 2, 3 }; int[] seq2 = { 3, 4, 5 }; seq1.Concat (seq2)
int[] seq1 = { 1, 2, 3 }; int[] seq2 = { 3, 4, 5 }; seq1.Union (seq2)
Distinct string[] names = { "Tom","Tom", "Dick", "Harry", "Mary", "Jay" , "Jay"}; IEnumerable<string> query = names.Distinct();
group by Developer[] developers = new Developer[] { new Developer { Name = "Paolo", Language = "C#" }, new Developer { Name = "Marco", Language = "C#" }, new Developer { Name = "Frank", Language = "VB.NET" }, }; var developersGroupedByLanguage = from d in developers group d by d.Language; foreach (var group in developersGroupedByLanguage) { Console.WriteLine("Language: {0}", group.Key); foreach (var item in group) { Console.WriteLine("\t{0}", item.Name); } }
group by Language: C# Paolo Marco Language: VB.NET Frank
group by 2 Developer[] developers = new Developer[] { new Developer { Name = "Paolo", Language = "C#", Age = 32 }, new Developer { Name = "Marco", Language = "C#", Age = 37}, new Developer { Name = "Frank", Language = "VB.NET", Age = 48 }, }; var developersGroupedByLanguage = from d in developers group d by new { d.Language, AgeCluster = (d.Age / 10) * 10 }; foreach (var group in developersGroupedByLanguage) { Console.WriteLine("Language: {0}", group.Key); foreach (var item in group) { Console.WriteLine("\t{0}", item.Name); } }
group by 2 Language: { Language = C#, AgeCluster = 30 } Paolo Marco Language: { Language = VB.NET, AgeCluster = 40 } Frank
group by 3 var developersGroupedByLanguage = from d in developers group d by d.Language into developersGrouped select new { Language = developersGrouped.Key, DevelopersCount = developersGrouped.Count() }; foreach (var group in developersGroupedByLanguage) { Console.WriteLine ("Language {0} contains {1} developers", group.Language, group.DevelopersCount); }
OfType System.Collections.ArrayList fruits = new System.Collections.ArrayList(4); fruits.Add("Mango"); fruits.Add("Orange"); fruits.Add("Apple"); fruits.Add(3.0); fruits.Add("Banana"); IEnumerable<string> query1 = fruits.OfType<string>(); foreach (string fruit in query1) { Console.WriteLine(fruit); } 运行结果: Mango Orange Apple Banana
Cast System.Collections.ArrayList fruits = new System.Collections.ArrayList(); fruits.Add("apple"); fruits.Add("mango"); IEnumerable<string> query = fruits.Cast<string>().Select(fruit => fruit); foreach (string fruit in query) { Console.WriteLine(fruit); } 运行结果: Apple mango
OfType vs Cast OfType: filters the source sequence, yielding only items of type TResult. It is useful in the case of sequences with items of different types. Cast: enumerates the source sequence and tries to yield each item, cast to type TResult. In the case of failure, an InvalidCastException error will be thrown.
Deferred Query Evaluation var numbers = new List<int>(); numbers.Add(1); IEnumerable<int> query = numbers.Select(n => n * 10); numbers.Add(2); foreach (var a in query){ Console.WriteLine(a);}
Deferred Query Evaluation static Boolean DoSomething(Developer dev) { if (dev.Age > 40) throw new ArgumentOutOfRangeException("dev"); return (dev.Language == "C#"); }
Deferred Query Evaluation Developer[] developers = new Developer[] { new Developer { Name = "Frank", Language = "VB.NET", Age = 48 }, … … }; var query = from d in developers let SomethingResult = DoSomething(d) select new { d.Name, SomethingResult }; try { foreach (var item in query) { Console.WriteLine(item); } } catch (ArgumentOutOfRangeException e) { Console.WriteLine(e.Message); }
总结 数据结构上的关联 数据上的关联 OrderBy Take Skip Reverse First,Last Count Min Any Contains Concat Union Distinct group by OfType Cast Deferred Query Evaluation