LINQ——语言级集成查询入门指南(1)
本文主要是对语言级集成查询或简称为LINQ做一个介绍,包括LINQ是什么,不是什么,并对它在语言特性方面做一个简短的回顾,然后举一些使用LINQ的实际例子进行说明。
语言级集成查询是什么?
在我过去写的大多数文章中,即使是最早的一篇文章(数据库独立的数据访问),也总是涉及到访问和操纵数据,通常,数据是存储在数据库中的,但也有其他种类访问和操纵数据如数据文件,事件日志,注册表等的方式,查询和操纵数据是许多应用程序通用的部分。
LINQ(经常听到有人发音与link一样)在数据访问方面向前推进了一大步,它是一个编程模型,无论是访问文件、XML、数据库、注册表、事件日志、活动目录,还是第三方如Flickr的数据,都使用统一的方法进行访问,它设计与所有不同形态,不同大小的数据一起工作,允许你在所有这些数据上执行查询,设置和转换。
LINQ不是什么
理解一下LINQ不是什么对于理解它是什么非常有帮助,我最常听见的是人们将LINQ认为是嵌入式SQL,我要说的是,它不是嵌入式SQL,尽管LINQ的语法与SQL语法在某些方面非常相似,但它的确不是嵌入式SQL,而且它也不局限于只能查询数据库,.NET语言大部分都不是自动就支持LINQ的,对常见语言运行时(CLR)不用做修改,修改的是语言和它们的编译器,它需要特定语言的扩展,Visual Basic .NET 9.0和C# 3.0已经集成了对LINQ的支持。
LINQ开启的语言特性
LINQ大量使用了类,此外,对Visual Basic和C#语言也进行了大量的扩充以支持LINQ,最近有许多文章介绍这些新的语言特性,作为LINQ的先兆,下面简要列出一部分LINQ语言特性帮助开启LINQ之路:
◆类型接口:代表不同类型的简写是编译时右边赋值的类型
◆扩展方法:扩展一个现有的值或引用类型而不产生一个新的类型
◆对象初始程序:对象初始化语法产生的等效代码的简写形式
◆匿名类型:不合成方法或类型创建声明
◆Lambda表达式:创建排队方法的简单途径
◆查询表达式:在代码中操作对象时与SQL类似
我确信这些语言特性对于LINQ自身肯定有益,但我还没有亲自找到理由在LINQ之外使用它们。
LINQ风格
在访问和操纵不同数据源方面LINQ有多种风格,后面的清单包括了一些由微软提供的数据域,它们中任何一个将来都可能成为.NET的核心话题:
◆面向对象的LINQ:操纵对象的集合
◆面向数据集的LINQ:使用LINQ操纵数据集
◆面向SQL的LINQ:在自定义类型和数据库表语句集之间建立映射
◆面向实体的LINQ:使用一个概念性的实体数据模型创建一个物理数据库的概念模型
◆面向XML的LINQ:允许查询和操纵XML
LINQ语法介绍
对于那些特别想知道如何组织和格式化代码的人而言,需要花一点时间来熟悉LINQ的语法,如何将其运用到自己的代码中去,对于那些已经听说过LINQ不是嵌入式SQL的人,在使用LINQ时可能需要花点时间来适应。
尽管LINQ不是只能访问数据库,我发现它在帮助人们理解LINQ时的价值,首先是检验SQL语句,然后再在代码中加入LINQ表达式完成同样的事情,下面的SQL语句是基于Microsoft SQL Server的Northwind示例数据库中构建的,这个查询非常简单,列出没有住在Berlin顾客。
SELECT c.CompanyName, c.ContactName, c.City FROM Customers c WHERE c.City != 'Berlin' ORDER BY c.ContactName
现在来看看用LINQ表达式做同样的事情,解剖并理解清除其中的细节,有两种查询语法:查询表达式和方法查询。目前,暂时先考虑查询表达式,下面的查询表达式将从GetCustomers()返回的结果查询IEnumerable类型,找出那些没有居住在Berlin的顾客。对于这个例子,你会认为是GetCustomers方法查询数据库并返回IEnumerable类型的。
var customerNotInBerlin = from c in GetCustomers() where c.City != "Berlin" orderby c.ContactName select c;
下面的表格概述了LINQ可用的部分选项
Destination 目标 |
var <变量> = |
使用类型推理来赋值 |
Source 源 |
from <项目> in <数据源> |
信息源提供一套项目 |
Filter 过滤器 |
where <表达式>, distinct |
表达式指定选择的标准 |
Order 排序 |
order by <表达式>, <表达式> [升序 |降序] |
控制结果的排序 |
Aggregate 合计 |
count([<表达式>]), sum(<表达式>), min(<表达式>), max(<表达式>), avg(<表达式>) |
合计源项目 |
Projection 投影 |
select <表达式> |
构造输出内容 |
还有更多的选项和语法变化,这里只是为了向你提供一个入门的介绍。
通过例子测试驱动LINQ
至此,你已经对LINQ有一点背景知识了,下面我们通过一个新的有用的例子来对LINQ进行测试,你已经看到如何使用LINQ从结构类型、文件或时间日志中读取数据。
访问结构类型
你可以使用之前的例子显示语法和查询结构类型,你将会创建一个Custome结构类型,并从Northwind数据库获取一些数据来填充Customer结构类型,你可以选择City不是Berlin的记录,然后在终端显示它们,因为你选择的是Customer对象,所以你可以使用ToString方法来格式化输出,使用逗号分隔显示属性。
using System; using System.Collections.Generic; using System.Linq; namespace LINQIntro { class Customer { public string CustomerName { get; set; } public string ContactName { get; set; } public string City { get; set; } public override string ToString() { return this.CustomerName + ", " + this.ContactName + ", " + this.City; } } class Program { static void Main(string[] args) { Program.ShowCustomers(); } public static void ShowCustomers() { //使用对象初始化程序建立一列customer List customers = new List { new Customer { CustomerName = "Alfreds Futterkiste", ContactName = "Maria Anders", City = "Berlin"}, new Customer { CustomerName = "Ana Trujillo Emparedados y helados", ContactName = "Ana Trujillo", City = "México D.F."}, new Customer { CustomerName = "Antonio Moreno Taquería", ContactName = "Antonio Moreno", City = "México D.F."}, new Customer { CustomerName = "Around the Horn", ContactName = "Thomas Hardy", City = "London"}, new Customer { CustomerName = "Berglunds snabbköp", ContactName = "Christina Berglund", City = "Luleå"}}; //查询customer列,指定显示字段 var customer = from c in customers where c.City != "Berlin" orderby c.ContactName select c; //在终端显示选择的记录 foreach (var row in customer) { Console.WriteLine(row); } Console.ReadLine(); } } }
显示一列文件
在这个例子中,将使用LINQ查询一个目录中以某个特定名字开始的文件,重复一篇,我是随机选择的目录和文件名,你可能想要修改查询语句中的目录和文件名以适应你的环境。
using System; using System.Collections.Generic; using System.Linq; namespace LINQIntro { class Program { static void Main(string[] args) { Program.ShowFiles(); } public static void ShowFiles() { //指定特定目录 System.IO.DirectoryInfo dirInfo = new System.IO.DirectoryInfo( "C:\\Program Files\\Microsoft Visual Studio 9.0"); //找出目录中的文件并选中它 var directoryList = from f in dirInfo.GetFiles("*.*", System.IO.SearchOption.AllDirectories) where f.Name.StartsWith("re") select f; //在终端显示选中的记录 foreach (var row in directoryList) { Console.WriteLine(row); } Console.ReadLine(); } } }
显示运行中的进程
在这个例子中,使用LINQ列出本地PC上正在运行的进程。
using System; using System.Collections.Generic; using System.Linq; namespace LINQIntro { class Program { static void Main(string[] args) { Program.ShowProcesses(); } public static void ShowProcesses() { //选择进程列表并计划输出 var processes = from p in System.Diagnostics.Process.GetProcesses() orderby p.ProcessName select new { p.ProcessName, p.Id, p.WorkingSet64, p.Threads, p.HandleCount }; //在终端显示选择的记录 foreach (var row in directoryList) { Console.WriteLine(row); } Console.ReadLine(); } } }
访问应用程序日志文件
在这个例子中,使用LINQ从应用程序日志中获取数据,可以在日志中搜索包含某个特定消息的错误日志,我从我本地计算机的事件日志中随机选了一个错误进行搜索,你可能想要调整查询语句以适应你的本地事件日志,对于那些不相信能用LINQ查询日志的人来说,这个例子向你展示了LINQ的能力和价值。
using System; using System.Collections.Generic; using System.Linq; namespace LINQIntro { class Program { static void Main(string[] args) { Program.ShowEventLog(); } public static void ShowEventLog() { //加入应用程序事件日志 System.Diagnostics.EventLog myLog = new System.Diagnostics.EventLog(); myLog.Log = "Application"; //使用terminated为关键字查询错误记录,你可能想要调整EventLogEntryType和 //消息以适应你本地的事件日志,注意自定义对象是如何形成的。 var logEntries = from System.Diagnostics.EventLogEntry e in myLog.Entries where e.EntryType == System.Diagnostics.EventLogEntryType.Error && e.Message.Contains("terminated") select new { e.Source, e.InstanceId, e.Message, e.TimeGenerated }; //显示选择的记录 foreach (var row in logEntries) { Console.WriteLine(row); } Console.ReadLine(); } }
小结
本文对LINQ是什么不是什么做了一个简要的概述,同时,你也看到了LINQ开启了新的语言特性,并通过一些例子对LINQ语法进行了介绍,希望本文给你带来了有用的知识,使你能在将来的工作中更好地运用LINQ。