LINQ语言集成查询(Language Integrated Query)
LINQ 最明显的“语言集成”部分是查询表达式。查询表达式是使用 C# 3.0 中引入的声明性查询语法编写的。通过使用查询语法,您甚至可以使用最少的代码对数据源执行复杂的筛选、排序和分组操作。您使用相同的基本查询表达式模式来查询和转换 SQL 数据库、ADO.NET 数据集、XML 文档和流以及 .NET 集合中的数据。
下面的示例演示了完整的查询操作。完整操作包括创建数据源、定义查询表达式,以及在 foreach 语句中执行查询。 class LINQQueryExpressions {
static void Main() {
// Specify the data source.
int[] scores = new int[] { 97, 92, 81, 60, , 45, 34, 78 };
// Define the query expression. IEnumerable foreach (int i in scoreQuery) { Console.Write(i + \" \"); } } } // Output: 97 92 81 2、 LINQ查询 “查询”是指一组指令,这些指令描述要从一个或多个给定数据源检索的数据以及返回的数据应该使用的格式和组织形式。查询不同于它所产生的结果。 通常,源数据会在逻辑上组织为相同种类的元素序列。SQL 数据库表包含一个行序列。与此类似,ADO.NET DataTable 包含一个 DataRow 对象序列。在 XML 文件中,有一个 XML 元素“序列”(不过这些元素按分层形式组织为树结构)。内存中的集合包含一个对象序列。 从应用程序的角度来看,原始源数据的具体类型和结构并不重要。应用程序始终将源数据视为一个 IEnumerable<(Of <(T>)>) 或 IQueryable<(Of <(T>)>) 集合。在 LINQ to SQL 中,源数据显示为一个 IEnumerable IEnumerable 检索一个元素子集以产生一个新序列,但不修改单个元素。然后,查询可以 按各种方式对返回的序列进行排序或分组,如下面的示例所示(假定 scores 是 int[]): IEnumerable orderby score descending select score; 如上一个示例所述检索一个元素序列,但是将这些元素转换为具有新类型的 对象。例如,查询可以只从数据源中的某些客户记录检索姓氏。或者,查询可以检索完整的记录,再使用它构建另一个内存中对象类型甚至 XML 数据,然后生成最终的结果序列。下面的示例演示了从 int 到 string 的转换。请注意 highScoresQuery 的新类型。 IEnumerable orderby score descending select String.Format(\"The score is {0}\", score); 检索有关源数据的单一值。 IEnumerable int scoreCount = highScoresQuery3.Count(); 3、 查询表达式 “查询表达式”是用查询语法表示的查询,是一流的语言构造。它就像任何其他表达式一样,并且可以用在 C# 表达式有效的任何上下文中。查询表达式由一组用类似于 SQL 或 XQuery 的声明性语法编写的子句组成。每个子句又包含一个或多个 C# 表达式,而这些表达式本身又可能是查询表达式或包含查询表达式。 查询表达式必须以 from 子句开头,并且必须以 select 或 group 子句结尾。在第一个 from 子句和最后一个 select 或 group 子句之间,查询表达式可以包含一个或多个下列可选子句:where、orderby、join、let 甚至附加的 from 子句。还可以使用 into 关键字使 join 或 group 子句的结果能够充当同一查询表达式中附加查询子句的源。 3.1 查询变量 在 LINQ 中,查询变量是任何存储查询而不是查询结果的变量。更具体地说,查询变量始终是一个可枚举的类型,当在 foreach 语句中或对其 IEnumerator.MoveNext 方法的直接调用中循环访问它时,它将产生一个元素序列。 3.2 查询变量的显式类型化和隐式类型化 使用 var 关键字指示编译器在编译时推断查询变量(或任何其他本地变量)的类型。 3.3 开始查询表达式 查询表达式必须以 from 子句开头。它同时指定了数据源和范围变量。在对源序列进行遍历的过程中,范围变量表示源序列中的每个后续元素。将根据数据源中元素的类型对范围变量进行强类型化。在使用分号或延续子句退出查询之前,范围变量将一直位于范围中。查询表达式可以包含多个 from 子句。当源序列中的每个元素本身就是集合或包含集合时,可使用附加的 from 子句。 3.4 结束查询表达式 查询表达式必须以 select 子句或 group 子句结尾。 group子句 使用 group 子句可产生按照指定的键组织的组序列。键可以采用任何数据类型。 select子句 使用 select 子句可产生所有其他类型的序列。 into延续 可以在 select 或 group 子句中使用 into 关键字来创建用于存储查询的临时标识符。 在下面的示例中,以一千万人口范围为界对 countries 进行分组。在创建 这些组之后,使用附加子句筛选掉某些组,然后按升序对剩下的组进行排序。若要执行这些附加操作,需要使用由 countryGroup 表示的延续。 var percentileQuery = from country in countries let percentile = (int)country.Population / 10000000 group country by percentile into countryGroup where countryGroup.Key >= 20 orderby countryGroup.Key select countryGroup; 3.5 筛选、排序和联接 在 from 开始子句以及 select 或 group 结束子句之间,所有其他子句(where、join、orderby、from、let)都是可选的。任何可选子句都可以在查询正文中使用零次或多次。 where 子句 使用 where 子句可以根据一个或多个谓词表达式筛选掉源数据中的某些元素。 orderby 子句 使用 orderby 子句可以按升序或降序对结果进行排序。您还可以指定次要排序顺序。 join 子句 使用 join 子句可以根据每个元素中指定键之间的相等比较,对一个数据源中的元素与另外一个数据源中的元素进行关联和/或组合。在 LINQ 中,联接操作是针对其元素具有不同类型的对象序列执行的。在联接两个序列之后,必须使用 select 或 group 语句指定要存储到输出序列中的元素。还可以使用匿名类型将每组关联元素中的属性组合为输出序列的新类型。 let 子句 使用 let 子句可以将表达式(如方法调用)的结果存储到新的范围变量中。 3.6 查询表达式中的子查询 查询子句本身可能包含一个查询表达式,该查询表达式有时称为“子查询”。每个子查询都以它自己的 from 子句开头,该子句不一定指向第一个 from 子句中的同一数据源。 var queryGroupMax = from student in students group student by student.GradeLevel into studentGroup select new { Level = studentGroup.Key, HighestScore = (from student2 in studentGroup select student2.Scores.Average()) .Max() }; 注意: 在 LINQ 文档中,存储查询的变量在其名称中包含单词“query”,而存储实际结果的变量在其名称中不包含单词“query”。 4、 BT.SaaS.UI.WidgetFramework中LINQ 的使用 以BT.SaaS.UI.WidgetFramework.DataAccess的DatabaseHelper类为例: 定义连接字符串 public const string ConnectionStringName = \"DashboardConnectionString\"; 定义ApplicationID public const string ApplicationID = \"fd639154-299a-4a9d-b273-69dc28eb6388\"; public readonly static Guid ApplicationGuid = new Guid(ApplicationID); 定义最大行数,防止查询大量结果导致服务器阻塞 public const int MAX_ROWS = 1000; public static DashboardDataContext GetDashboardData() { return GetDashboardData(true, MAX_ROWS); } 重载GetDashboardData()方法 public static DashboardDataContext2 GetDashboardData(bool readOnly, int maxRowsAllowedToAffect) { 读取数据库连接配置 var db = new DashboardDataContext2(ConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString); 设置事物隔离级别为 READ UNCOMMITTED if (readOnly) { db.Connection.Open(); db.ExecuteCommand(\"SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET NOCOUNT ON; SET ROWCOUNT \" + maxRowsAllowedToAffect); } 设置事物隔离级别,防止在提交事务之前记录受其他用户的影响,避免 了幻觉读 else { db.Connection.Open(); db.ExecuteCommand(\"SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; SET ROWCOUNT \" + maxRowsAllowedToAffect); } 返回 db 连接对象 return db; } 更新操作 public static void Update { using (var db = GetDashboardData(false, 1)) { detach(obj); db.GetTable db.SubmitChanges(); } } 更新所有 public static void UpdateAll Action using (var db = GetDashboardData(false, items.Count)) { Table detach(item); table.Attach(item); update(item); } } } db.SubmitChanges(); 添加操作 public static void Insert public static void Delete using (var db = GetDashboardData(false, 1)) { } Table table.DeleteOnSubmit(entity); db.SubmitChanges(); { } using (var db = GetDashboardData(false, 1)) { } db.GetTable 如何使用 DatabaseHelper 示例1:Insert 方法 protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) WidgetInstance wi = new WidgetInstance(); wi.Title = w.Name; wi.PageId = PageId; wi.CreatedDate = wi.LastUpdate = DateTime.Now; wi.VersionNo = 1; { using (var db = DatabaseHelper.GetDashboardData()) { Widget w = db.Widgets.Single(a => a.ID == WidgetId); } } wi.State = string.Empty; wi.WidgetId = w.ID; wi.Expanded = true; wi.State = w.DefaultState; DatabaseHelper.Insert return ActivityExecutionStatus.Closed; 示例2:Update 方法 protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) { DatabaseHelper.Update (wi) => wi.Detach(), (wi) => wi.State = this.State); 5、 LINQ 用法示例。 } return ActivityExecutionStatus.Closed; 以Northwind数据库为例,使用LINQ to SQL设计器设计以上定义的五个 类(Product,Category,Customer,Order和OrderDetail)的时候,每个类中的属性都映射了相应数据库中表的列,每个类的实例则代表了数据库表中的一条记录。另外,当定义数据模型时,LINQ to SQL设计器同样会创建一个自定义DataContext类,作为数据库查询和应用更新/变化的主要渠道。以上数据模型中定义的DataContext类命名为 NorthwindDataContext。该类中包含了代表每个建模数据库表的属性。 5.1 检索操作 使用LINQ语法表达式可以十分简单的使用NorthwindDataContext类来 查询和检索数据库中的数据。LINQ to SQL会在运行时自动的转换LINQ表达式到适当的SQL代码来执行。例如,编写以下LINQ表达式来根据Product Name检索单个Product对象: 还可以使用LINQ表达式来检索所有不存在于Order Details中的,并且UnitPrice大于100的所以Product: 当执行查询和检索像Product实例这样的对象时,LINQ to SQL会自动保 持对这些对象任何变化或更新的跟踪。我们可以进行任意次数的查询,以及使用LINQ to SQL的DataContext类做出更新,而这些变化都会被全部跟踪。 注意:LINQ to SQL的变化跟踪发生于调用者端而不是在数据库中。这就意 味着使用跟踪不会销耗任何数据库资源,也不需要在数据库中改变/安装任何组件模块。 5.2 更新操作 当对从LINQ to SQL中检索的对象做出更改之后,我们可以选择调用 DataContext上的SubmitChange()方法来应用变化返回到数据库。这将会导致LINQ to SQL动态计算并执行适当的SQL代码来更新数据库。例如,编写以下代码更新数据库中Product Name为“Chai”的Product上的UnitPrice和UnitsInStock: 在下面代码中我们来遍历OrderDetails.Count 等于0,UnitPrice大于100 的Product,并把它们的Reorder Level属性设为0: 当在以上代码中调用northwind.SubmitChanges()方法时,LINQ to SQL 会计算并执行一组适当的UPDATE代码模块来修改RecorderLevel属性已变化的Product。 注意,如果一个Product的属性没有通过属性指定而发生变化,则该对象不 会被认为是发生变化的,并且LINQ to SQL也不会对于该对象执行更新回数据库的操作。例如,如果“Chai”对象的UnitPrice仍旧是$2,UnitsInStock仍旧是4,当调用SubmitChange()时不会导致任何数据库UPDATE代码模块的执行。相似的,在第二个例子中的那些符合条件的Product中只有RecorderLevel原来不是0的才会在SubmitChange()被调用时更新。 5.3 通过关系关联更新 我们很简单的通过表之间的关系关联来对数据模型建模。例如,可以把每个 Product建模到一个Category中,每个Order包含多条OrderDetail明细,每条OrderDetail明细都关联着一个Product,并且每个Customer拥有一组相关联的Order。 LINQ to SQL能够让我们不论是在查询还是更新数据中都可以利用这些关 系关联。例如,编写以下代码来创建一个新Product,并关联到一个数据库中已存在的“Beverages”Category上: 注意如何添加Product对象到该Category的Product集合中。这样就会 指明这两个对象之间存在关系关联,并会导致LINQ to SQL在调用SubmitChange()时,自动维护两者之间的主/外键关系关联。 另一个LINQ to SQL有助于管理交叉表关系关联的例子,让我们看一下如 何对一个现有的Customer创建一个新的Order。在设置Order的OrderDate和RequireDate和Freight后,然后创建两个Customer订购的Product相关的OrderDetail,并添加到Order中,随后关联Order到Customer上,最后 更新所有变化回数据库: 6、 LINQ数据验证 LINQ to SQL为开发者提供了多种途径,可以十分简洁地把这些整合到数据 模型中。LINQ to SQL会确保一旦添加数据验证逻辑,就可以无论何时何地在都应用于数据模型上。这就避免了在多处的重复定义,保证了数据模型的可维护 性和代码整洁。 6.1 框架验证 使用LINQ to SQL设计器定义数据模型类时,会被默认附加一些根据数据 库表构架推断出的验证规则。数据模型类中的属性的数据类型会与数据库构架的数据类型相匹配。这就意味着如果试图指定一个boolean值到decimal,或试图隐式转换numeric类型都会导致编译错误。如果数据库中的列可以为null,则通过LINQ to SQL设计器创建的在数据模型中的相应的属性会是一个Nullable类型。如果试图给未标记为Nullable的属性指定null值,会自动引发异常。相似地,LINQ to SQL也会确保数据库中的identity/unique列值的正确验证。 也可以使用LINQ to SQL设计器来覆写这些默认的构架驱动的验证设置, 但是通过默认我们可以自动获得它们,而且不需要进行额外的工作。LINQ to SQL同样会自动处理转义的SQL值,这样就避免SQL注入攻击了。 6.2 自定义属性验证 但在应用中如对于下面的代码,从单纯的SQL构架方面来说是合法的: 为防止虚假的电话号码被添加进数据库,我们可以添加一个自定义属性验证 规则到Customer数据模型类中。使用局部类的特性,可以十分简单的添加规则来验证电话号码,只需要添加一个新的包含如下方法定义的局部类到我们的项 目中: 6.3 自定义实体对象 属性级别的验证对于验证数据模型类上的属性是十分有效的。但有时却 需要同时验证一个对象上的多个属性。考虑这样一个场景,同时设置Order对象上的OrderDate和RequiredDate属性: 以上的代码从单纯的SQL构架方面来说是合法的——即使规定交货日期是 订单下达日期的前一天是毫无意义的。 LINQ to SQL可以十分简单地添加自定义实体级别的验证规则来防止类似 的错误发生。可以为Order实体类添加一个局部类,并实现OnValidate()局部方法,该方法会在实体的值将要记录进数据库的时候被调用。使用该验证方法可以访问和验证所有的数据模型类属性。 使用该验证方法我们可以检查实体类上的任何属性值,如果验证的值不正确 就可以产生一个异常。从OnValidate()方法中产生的任何异常都导致从数据库更新操作中退出,并且回滚该操作所在事务中的所以更改。 6.4自定义实体插入/更新/删除方法验证 对于特定的插入,更新或删除场景来添加验证逻辑。LINQ to SQL可以通过 添加局部类型来扩展DataContext类,然后实现相应的局部方法来为数据模型实体自定义插入,更新和删除逻辑。当调用DataContext类上的SubmitChanges()方法时,这些方法会被自动调用。
因篇幅问题不能全部显示,请点此查看更多更全内容