【IT168 技术文档】
目录
LINQ 项目: 对象和关系型查询的统一语言特性
LINQ 项目: 对象和关系型查询的统一语言特性
本实验将介绍LINQ项目。面向.NET的语言集成查询框架(“LINQ”)是一个C#和VB的语言扩展,并是一个统一的编程模型,它扩展了.NET Framework来提供面向对象、数据库和XML的集成查询。
在本实验中,您将看到如何使用LINQ特性访问内存中的集合、XML文档和连接的数据库。实验的最后,是一个可选的练习,带您浏览了用来数据处理和抽取的不同标准查询运算符。
实验目标
完成本实验估计需要:90 分钟
本实验的目标是为了让您更为明确的理解LINQ项目。您将看到如何对内存中的对象和关系型数据库进行数据处理。新的LINQ API受益于智能感知功能,并可以得到完全的编译时检查,而无需考虑基于字符串的查询。
练习1 –面向内存中集合的LINQ
在本练习中,您将学习如何查询对象序列。所有支持System.Collections.Generic.IEnumerable接口或范型接口IEnumerable<T>的集合都被认为是一个序列,并且可以使用新的LINQ标准查询运算符进行操作。标准查询运算符允许程序员来构建查询,包括在进行投影时创建新的类型。这与类型接口的功能是紧密相关的,它允许变量由它的初始化表达式来标定类型。
任务1 –创建 “LINQ Overview” 解决方案
点击Start | Programs | Microsoft Visual Studio 2008| Microsoft Visual Studio 2008菜单项,打开Visual Studio 2008。
点击File | New | Project… 菜单命令。
在 New Project 对话框中,选择Visual C# Windows 项目类型。
选择Console Application 项目模板。
为新建的项目指定名称,在Name框中输入“LINQ Overview”。
点击OK。
任务 2 – 查询一个整数类型的列表
在 Solution Explorer中,双击Program.cs
创建一个新的方法,声明一个整数类型的集合(将该方法放到Program类中):
{
static void Main(string[] args)
{
}
static void NumQuery()
{
var numbers = new int[] { 1, 4, 9, 16, 25, 36 };
}
}
注意,赋值式子左边并没有使用一个显式的类型声明;而是使用了一个新的关键字var。这是C# 3.0里面的一个新的功能,隐匿的本地类型声明。这个功能允许本地变量由编译器来推断出它的类型。在这里,右边创建了一个Int32[]类型的对象,因此编译器将推断出它的类型是Int32[]。这样也使得一些变量在初始化的时候才得到它的类型名称。
添加下面的代码,来查询集合中的所有偶数。
{
var numbers = new int[] { 1, 4, 9, 16, 25, 36 };
var evenNumbers = from p in numbers
where (p % 2) == 0
select p;
}
在这个步骤中,赋值式子右边的是一个查询表达式,是LINQ项目所引入的另一个语言扩展。和上面的步骤中的一样,使用类型推断来简化代码。查询返回的类型可能并不是非常明显。这个示例将返回System.Collections.Generic.IEnumerable<Int32>类型;将鼠标移动到evenNumbers上可以在Quick Info中看到类型。确实,有时候没有办法指定类型,这样就需要指定匿名的类型(由对象的初始化代码自动推断和创建的类型)。类型推断为这个问题提供了一种简单的解决方案。
添加下面的代码以显示结果:
{
var numbers = new int[] { 1, 4, 9, 16, 25, 36 };
var evenNumbers = from p in numbers
where (p % 2) == 0
select p;
Console.WriteLine("Result:");
foreach (var val in evenNumbers)
Console.WriteLine(val);
}
注意,foreach 语句已经被扩展到可以使用类型推断了。
最后,向Main函数中添加对NumQuery方法的调用:
{
NumQuery();
}
点击Ctrl+F5 来构建和运行应用程序。这时将会出现一个控制台。正如预期的那样,所有的偶数将会被显示出来(包括数字4、16、和36 将会出现在控制台的输出中)。
点击任意键,以结束应用程序。
任务 3 – 查询结构化类型
在本任务中,您将不再使用基本类型,而是要将查询功能应用到自定义的结构类型当中。在Program类的声明上面,添加下面的代码以创建一个Customer类:
{
public string CustomerID { get; set; }
public string City { get; set; }
public override string ToString()
{
return CustomerID + "\t" + City;
}
}
class Program
…
注意,这里使用到了C#语言的另外一个功能,自实现(auto-implemented)属性。在上面的代码中,使用这种功能创建了两个属性,它们将会自动包含后端的字段。另外,要注意的是,没有声明构造函数。在早期的C#版本中,如果这样去构造类的话,就需要类的调用者使用默认的无参数构造函数创建一个对象实例,然后再使用单独的语句分别设置它的字段与属性值。
Within the Program class declaration, create the following new method, which creates a list of customers (taken from the Northwind database):
在Program类的声明中,创建下面的方法,它将创建一个客户列表(该数据是从Northwind数据库中获取出来的):
{
NumQuery();
}
static IEnumerable<Customer> CreateCustomers()
{
return new List<Customer>
{
new Customer { CustomerID = "ALFKI", City = "Berlin" },
new Customer { CustomerID = "BONAP", City = "Marseille" },
new Customer { CustomerID = "CONSH", City = "London" },
new Customer { CustomerID = "EASTC", City = "London" },
new Customer { CustomerID = "FRANS", City = "Torino" },
new Customer { CustomerID = "LONEP", City = "Portland" },
new Customer { CustomerID = "NORTS", City = "London" },
new Customer { CustomerID = "THEBI", City = "Portland" }
};
}
在上面的代码段中,还有一些非常有趣的地方值得注意一下。首先,注意到新建集合时直接使用大括号来创建的。也就是说,即便它的类型是List<T>,而不是数组,也可以使用大括号来进行声明并添加元素,不用再像以前一些使用Add方法。另外,注意到Customer元素也是使用一种新的语法来创建的(叫做对象初始化器(object initializer)。即使Customer类没有包含两个参数的构造函数,也可以像表达式一样,通过在大括号中显式的设定它的属性,来创建这个类的对象。
接下来,查询集合以找到居住在伦敦的所有客户。添加下面的查询方法ObjectQuery,并在Main方法中添加对它的调用(删除对StringQuery的调用)。
{
var results = from c in CreateCustomers()
where c.City == "London"
select c;
foreach (var c in results)
Console.WriteLine(c);
}
static void Main(string[] args)
{
ObjectQuery();
}
需要再次注意的是,编译器使用类型推断来强类型化foreach循环中的results变量。
点击Ctrl+F5 以构建并运行应用程序。查看完结果后,点击任意键以结束应用程序。
将会显示出三个结果。正如您所看到的那样,当使用LINQ查询表达式时,处理复杂的类型的过程与处理基本类型的过程是一样。
练习 2 – LINQ to SQL: 面向连接数据库的LINQ
本练习演示了LINQ to SQL当中更多的高级功能。
LINQ to SQL 是LINQ项目的一部分,允许您查询和处理与数据库表相关的对象。它消除了传统的数据库表与应用程序特定对象模型之间的不匹配,让您可以把数据当作对象来处理,同时框架自动的处理您的对象的获取和更新。
为了给一个特定数据库创建一个对象模型,我们必须将类映射到数据库实体当中。有三种方式可以创建对象映射:为现有的对象添加属性,使用提供的设计器来自动生成对象和映射,以及使用命令行的SQLMetal工具。本练习中将介绍前面的两种方法。
任务 1 – 创建对象映射– 创建一个对象并提供属性
在Solution Explorer中,右键点击References,选择“Add Reference”。
在弹出的菜单中,选择“System.Data.Linq”。
点击“OK”。
向program.cs 的顶部添加下面的using指令:
using System.Data.Linq;
using System.Data.Linq.Mapping;
为Customer添加下面的属性,以创建一个到数据库中的Customer表的映射,它包含了叫做CustomerID和City的两个列。在这里,您将只需要映射Northwind数据库中的Customer表的两个列。
public class Customer
{
[Column]
public string CustomerID { get; set; }
[Column]
public string City { get; set; }
public override string ToString()
{
return CustomerID + "\t" + City;
}
}
回到ObjectQuery方法。正如您在内存中的集合、XML和数据集中操作的那样,仍然是查询那些居住在伦敦的客户。注意,这里仅仅需要少量的代码变更。在创建完数据连接后,您可以获取出Customer表中的数据行,并选择那些居住在伦敦的客户所在的行,将它们作为IEnumerable<Customer>返回。
{
var db = new DataContext
(@"Data Source=(local);Initial Catalog=Northwind;Integrated Security=true;");
var results = from c in db.GetTable<Customer>()
where c.City == "London"
select c;
foreach (var c in results)
Console.WriteLine("{0}\t{1}", c.CustomerID, c.City);
}
ObjectQuery方法中使用的DataContext对象,正是从数据库中获取对象并提交数据更改的主要管道。
返回到Main 方法,然后对ObjectQuery做出如下更改:
{
ObjectQuery();
}
点击Ctrl+F5 来构建和运行应用程序。 查看输出的结果后,点击任意键以结束应用程序。
现在,添加下面的代码行,用来打印自动生成的运行在数据库中的SQL查询。
{
DataContext db = new DataContext(
@"Data Source=(local);Initial Catalog=Northwind;Integrated Security=true;");
db.Log = Console.Out;
var results = from c in db.GetTable<Customer>()
where c.City == "London"
select c;
foreach (var c in results)
Console.WriteLine("{0}\t{1}", c.CustomerID, c.City);
}
点击Ctrl+F5 来构建和运行应用程序。 查看输出的结果和生成的SQL查询后,点击任意键以结束应用程序。
任务 2 –创建对象映射– 使用设计器 – 添加设计器文件
首先需要删除原来的映射关系。删除整个Customer类。
接下来,创建对象,从而为数据表建模。右击LINQ Overview项目,并点击Add | New Item。
在Templates 中,点击LINQ To SQL Classes。
为新项提供一个名称,在Name文本框中输入“Northwind”。
点击OK。
任务 3 –创建对象映射–使用设计器– 创建对象视图
在Server Explorer中展开Data Connections。
展开Northwind 目录。
展开Tables 目录。
在Solution Explorer 中,双击Northwind.dbml 文件,以打开它。
从Server Explorer中的Tables 目录中,将Customers 表拖拽到打开的Northwind.dbml 设计器的面板上。
从Server Explorer中的Tables 目录中,将Products表拖拽到打开的Northwind.dbml 设计器的面板上。
从Server Explorer中的Tables 目录中,将Employees表拖拽到打开的Northwind.dbml 设计器的面板上。
从Server Explorer中的Tables 目录中,将Orders表拖拽到打开的Northwind.dbml 设计器的面板上。
从Server Explorer中的Stored Procedures目录中,将Top Most Extensive Products表拖拽到打开的Northwind.dbml 设计器的面板上。
点击Ctrl+Shift+B 以构建应用程序。这时,Program.cs中的Customer类将会与设计器生成的类冲突,将Program.cs中的Customer类删掉。
构建完成后,展开Solution Explorer中的Northwind.dbml,查看其对应的.cs文件。这是自动生成的映射类文件。注意,其中使用了与前面类似的属性。
对于包含许多表和存储过程的数据库来说,使用命令行工具SQLMetal可以提供更加强大的自动化支持,您可以选择使用,本实验将不包含该工具的介绍。
任务 4 – 使用表达式进行查询
在Solution Explorer中双击program.cs,回到程序代码当中。找到前面创建的ObjectQuery方法。现在,每个表都可以通过变量db的属性来进行访问。然后,查询的代码几乎与前面练习中使用的代码是一样的。添加下面的代码,以获取伦敦的客户:
{
var db = new NorthwindDataContext();
db.Log = Console.Out;
var results = from c in db.Customers
where c.City == "London"
select c;
foreach (var c in results)
Console.WriteLine("{0}\t{1}", c.CustomerID, c.City);
}
在这里,创建了一个NorthwindDataContext 对象(它扩展了前面第一个任务中使用的DataContext 类),用来表示一个强类型的数据库连接。值得注意的是,您不需要再指定连接字符串,智能提示将显示出设计器中所有的数据表。
点击Ctrl+F5 来构建和运行应用程序。在查看输出结果后,点击任意键以结束应用程序。
这时将显示出6个结果。这些客户在Northwind数据库中的Customer表中的City列都是London。
在使用设计器创建映射关系时,您也为其它数据表创建了映射。Customer类拥有一个到Orders表的一对多的映射。下面的代码将会查询多个表。
{
var db = new NorthwindDataContext();
db.Log = Console.Out;
var results = from c in db.Customers
from o in c.Orders
where c.City == "London"
select new { c.ContactName, o.OrderID };
foreach (var c in results)
Console.WriteLine("{0}\t{1}", c.ContactName, c.OrderID);
}
Select语句使用一个匿名类型(C# 3.0中的新功能)创建了一个新的对象。这个新建的类型包含了两部分的数据,包括原始数据中的属性名称(在这里,是ContactName和OrderID)。匿名数据在查询中使用非常方便。通过使用这些类型,它将自动完成创建结果类的工作,使得它保存不同查询的结果。
在前面的示例中,对象模型可以简单的通过调用c.Orders来引用对象之间的关系。这个关系是在设计器中定义的一对多的关系,而现在可以以这种方式来访问。
点击Ctrl+F5 来构建并运行应用程序。查看输出的结果,然后点击任意键以结束应用程序。
任务 5 – 修改数据库当中的数据
在这个任务中,您将会进行数据的获取并查看如何处理数据。四个基本的数据操作是Create、Retrieve、Update和Delete,和起来被称为CRUD。您可以看到LINQ to SQL是如何简单并直观的实现CRUD操作的。这个任务显示了如何使用create和update操作。
创建一个新的方法ModifyData,它修改数据库中的数据。然后,从Main函数中对该方法进行调用:
{
ModifyData();
}
static void ModifyData()
{
var db = new NorthwindDataContext();
var newCustomer = new Customer
{
CompanyName = "AdventureWorks Cafe",
CustomerID = "ADVCA"
};
Console.WriteLine("Number Created Before: {0}",
db.Customers.Where( c => c.CustomerID == "ADVCA" ).Count());
db.Customers.InsertOnSubmit(newCustomer);
db.SubmitChanges();
Console.WriteLine("Number Created After: {0}",
db.Customers.Where( c => c.CustomerID == "ADVCA" ).Count());
}
点击Ctrl+F5 来构建和运行应用程序。 注意,在数据库更新后输出到屏幕上的数据是不同的。然后,点击任意键以结束应用程序。
注意,在Add方法被调用后,将通过使用SubmitChanges方法,将变更提交到数据库。注意,一旦客户被插入后,它将由于主键唯一约束的问题,不能再被插入到数据库。因此,该程序只能运行一次。(或者,您可以修改插入的数据,再执行该程序。)
现在,将更新并修改数据库中的数据。添加下面的代码,它将为获取出来的第一个客户修改Contact name。
{
var db = new NorthwindDataContext();
var existingCustomer = db.Customers.First();
Console.WriteLine("Number Updated Before: {0}",
db.Customers.Where( c => c.ContactName == "New Contact" ).Count());
existingCustomer.ContactName = "New Contact";
db.SubmitChanges();
Console.WriteLine("Number Updated After: {0}",
db.Customers.Where( c => c.ContactName == "New Contact" ).Count());
}
现在,点击Ctrl+F5 来构建和运行应用程序。注意,拥有 “New Contact” 名称的联系人的数据发生了变化。点击任意键以结束应用程序。
任务 6 – 调用存储过程
Using the designer, recall adding a stored procedure along with the tables. In this task you call stored procedures.
回忆一下,在前面的任务中我们曾经使用设计器添加了一个存储过程。在这个任务中,您将调用存储过程。
创建一个新的方法,来打印出Top Most Expensive Products存储过程的调用结果,该存储过程曾经使用设计器被添加到NorthwindDataContext当中。
{
var db = new NorthwindDataContext();
foreach (var r in db.Ten_Most_Expensive_Products())
Console.WriteLine(r.TenMostExpensiveProducts + "\t" + r.UnitPrice);
}
现在,在Main 函数中调用这个方法:
{
InvokeSproc();
}
点击Ctrl+F5 来构建和运行应用程序。在查看输出结果后,点击任意键以结束应用程序。
当数据库不能通过动态的SQL语句访问时,您可以使用C# 3.0和LINQ来运行存储过程,访问数据库当中的数据。
任务 7 – 扩展查询表达式
至此,本实验中演示的查询都只进行了最为基本的筛选。但是,LINQ支持许多选项来查询数据,这不仅仅是简单的筛选。例如,为了对伦敦的所有客户根据他们的ContactName进行排序,您可以使用orderby子句:
{
var db = new NorthwindDataContext();
db.Log = Console.Out;
var results = from c in db.Customers
where c.City == "London"
orderby c.ContactName descending
select new { c.ContactName, c.CustomerID };
foreach (var c in results)
Console.WriteLine("{0}\t{1}", c.CustomerID, c.ContactName);
}
static void Main(string[] args)
{
ObjectQuery();
}
点击Ctrl+F5 来构建和运行应用程序。注意,客户是根据第二列 - 名称列,按降充的方式进行排序的。点击任意键以结束应用程序。
可以继续进行不同类型的查询:编写一个查询,计算出位于每个城市的客户数量。为了完成这个任务,您需要使用group by表达式。
{
var db = new NorthwindDataContext();
db.Log = Console.Out;
var results = from c in db.Customers
group c by c.City into g
orderby g.Count() ascending
select new { City = g.Key, Count = g.Count() };
foreach (var c in results)
Console.WriteLine("{0}\t{1}", c.City, c.Count);
}
点击Ctrl+F5 以运行应用程序。查看输出的结果后,, 点击任意键以结束应用程序。
通常,当编写查询时,您需要通过两个表进行搜索。通常可以使用一个join运算符来实现,该运算符在C# 3.0中受到支持。在ObjectQuery中,使用下面的代码替换原来的代码。在前面的任务中,您曾经将所有居住在伦敦的客户的所有订单都查询并打印了出来。在这里,我们不再打印所有的订单,而是打印出每个客户的订单的数量。
{
var db = new NorthwindDataContext();
db.Log = Console.Out;
var results = from c in db.Customers
join e in db.Employees on c.City equals e.City
group e by e.City into g
select new { City = g.Key, Count = g.Count() };
foreach (var c in results)
Console.WriteLine("{0}\t{1}", c.City, c.Count);
}
点击 Ctrl+F5 以运行应用程序。查看输出的结果,以及输出的SQL查询。点击任意键以结束应用程序。
这个示例展示了当数据间没有显式的关系时,如何使用一个SQL样式的联结。
实验总结
在本实验中,您完成了下列练习:
LINQ to Objects: 面向内存中集合的LINQ
LINQ to XML: 面向XML文档的LINQ
LINQ to DataSets: 面向DataSet对象的LINQ
LINQ to SQL: 面向连接数据库的LINQ
本实验展示了LINQ框架及功能如何无缝的集成来自不同数据源的数据访问与处理。LINQ允许您利用SQL的功能和C#的灵活性,来处理内存中的对象。LINQ to SQL和LINQ to DataSets利用这种支持来将您的对象连接到数据库表和数据。最后,LINQ to XML利用XPath的功能实现了XML查询,并简化了C#代码。众多的标准查询运算符为数据处理提供了内置的支持,有些功能在早期的版本中可能需要再去扩展自定义的代码。使用C#与LINQ,查询数据并将数据转换到不同的格式也比以往更加简单。