语言集成查询(LINQ)允许您使用类似 SQL 的语法来查询数据。LINQ 可以用于许多不同类型的数据,微软和第三方都建立了 LINQ 提供商来访问广泛的数据源。本章通过向您展示如何使用 LINQ 对象来缩小列表范围。一旦你知道了 LINQ to Objects,理解其他 LINQ 提供者就很容易了,因为有相似的语法。
在编写任何 LINQ 代码之前,记得在文件顶部的System.Linq
名称空间中添加一个using
声明。本章中的每个示例都将使用以下类,其中包含要使用的集合:
using System.Collections.Generic;
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
}
public class Order
{
public int CustomerID { get; set; }
public string Description { get; set; }
}
public static class Company
{
static Company()
{
Customers = new List<Customer>
{
new Customer { ID = 0, Name = "May" },
new Customer { ID = 1, Name = "Gary" },
new Customer { ID = 2, Name = "Jennifer" }
};
Orders = new List<Order>
{
new Order { CustomerID = 0, Description = "Shoes" },
new Order { CustomerID = 0, Description = "Purse" },
new Order { CustomerID = 2, Description = "Headphones" }
};
}
public static List<Customer> Customers { get; set; }
public static List<Order> Orders { get; set; }
}
代码清单 95
这些是内存中对象的集合。为了使集合更容易查询,Company
是一个static
类,带有一个初始化static
属性的static
构造函数。如果抽象这个概念,数据可能是从文件、数据库或 REST 服务中读取的。无论数据源或 LINQ 提供商如何,基本的 LINQ 语法都保持不变。
查询数据只需要from
和select
两个关键词。记得为System.Linq
命名空间添加一个using
子句。语法看起来像 SQL,如下面的例子所示。
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public void Main()
{
IEnumerable<Customer> customers =
from cust in Company.Customers
select cust;
foreach (Customer cust in customers)
Console.WriteLine(cust.Name);
}
}
代码清单 96
LINQ 对对象的查询导致类型IEnumerable<T>
的集合。在这种情况下,它是Customer
对象的集合。from
关键字指定了一个范围变量cust
,它保存集合中的每个对象。您可以在in
关键字后指定集合。
select
定义要查询的内容。在这个例子中,你只是返回了整个对象。事实上,你得到的收藏和Company.Customers
里的是一样的。这在对象的 LINQ 中并不特别有用,但是如果数据是从外部数据源读取的,就非常有用,比如一个数据库,您只想将一组对象放入内存中进行进一步操作。select
允许你将你得到的数据重新组合成各种预测。以下是获取客户名称的查询。
IEnumerable<string> customers2 =
from cust2 in Company.Customers
select cust2.Name;
代码清单 97
select
使用cust2
变量访问Name
,得到一个string
(T4)属性类型的集合。有时,您需要一个完全不同的对象,该对象可以定义为:
public class CustomerViewModel
{
public string Name { get; set; }
}
代码清单 98
一个新的投影可以写成:
IEnumerable<CustomerViewModel> customerVMs =
from custVM in Company.Customers
select new CustomerViewModel
{
Name = custVM.Name
};
代码清单 99
这里,select
实例化了一个新的CustomerViewModel
。然后,它使用对象初始化语法填充值,将custVM.Name
分配给新对象的Name
属性。这就产生了一个集合类型CustomerViewModel
。
前面的示例假设您需要使用特定类型的集合。然而,如果你不关心集合是什么类型,如果你不想创建一个新的类仅仅是为了在一个算法中进行操作呢?在这种情况下,您可以使用匿名类型,如下面的清单所示。
var customers3 =
from cust3 in Company.Customers
select new
{
Name = cust3.Name
};
foreach (var cust3 in customers3)
Console.WriteLine(cust3.Name);
代码清单 100
匿名类型没有可以使用的名称,尽管 C# 可能会创建一个内部名称供自己使用。要解决这个问题,请使用var
关键字作为类型。注意投影如何使用没有类型名的new
:匿名类型。您可以为匿名类型定义任何您想要的属性;把它们写进去。还要注意,您可以在foreach
循环中使用var
。
如果需要从方法返回集合,请创建一个新的(命名的)类型并将其投影到该类型中。匿名类型是为限制其使用范围的情况而设计的。您将在代码的其他地方看到var
关键字,但是它被添加到语言中的原因是为了支持这种场景。下面的列表显示了使用var
的一种常见方式,不同于前面的场景。
var customer = new Customer();
代码清单 101
前面的语句比指定变量的对象类型要短,这种情况下是多余的,明显是Customer
。然而,下面的例子不太明显。
var response = DoSomethingAndReturnResults();
代码清单 102
前面陈述的问题是,仅仅阅读代码并不能告诉你var
是什么类型。你不知道它是一个单一的对象还是一个集合。在这种情况下,通过指定类型,代码可能更容易维护。
| | 注意:一个常见的误解是 var 很危险,因为它的行为类似于 object,允许您将变量设置为任何类型。这不是真的。当您使用 var 时,代码仍然是强类型的。一旦为 var 类型的变量赋值,就不能为该变量赋值任何其他类型。在前面的示例中,customers 是 Customer 类型的一个实例。您不能在以后编写代码来将另一个实例类型(例如,订单类型)的对象分配给该变量。 |
您可以使用where
子句过滤集合,如下例所示。
var customers4 =
from cust4 in Company.Customers
where cust4.Name.Length > 3 && !cust4.Name.StartsWith("G")
select cust4;
foreach (var cust4 in customers4)
Console.WriteLine(cust4.Name);
代码清单 103
在前面的列表中,客户的姓名必须长于3
,这将列表过滤到Gary
和Jennifer
。&&
运算符右侧的子句过滤列表,甚至进一步过滤第一个字符不是"G"
的名称。
在对象的 LINQ 中,您可以在where
子句中使用逻辑运算符、用于分组的括号以及任何其他逻辑来创建复杂的条件来过滤结果。您甚至可以调用另一个方法来评估正在评估的当前对象。where
条款的结果必须评估为bool
。其他 LINQ 提供商可能会限制where
子句中的表达式类型,因此您必须查看该特定提供商的文档才能了解更多信息。
在 LINQ,orderby
子句允许您对收集结果进行排序。下面的清单演示了这一点。
var customers5 =
from cust5 in Company.Customers
orderby cust5.Name descending
select cust5;
foreach (var cust5 in customers5)
Console.WriteLine(cust5.Name);
代码清单 104
在本例中,orderby
子句按照descending
顺序按客户名称对列表进行排序。默认顺序是升序,您可以省略descending
或指定ascending
来代替。输出结果是:
May
Jennifer
Gary
有时,数据库中会有两个不同的对象或相关表的集合,您需要将它们连接在一起。为此,请使用join
子句。
var customerOrders =
from cust in Company.Customers
join ord in Company.Orders
on cust.ID equals ord.CustomerID
select new
{
ID = cust.ID,
Customer = cust.Name,
Item = ord.Description
};
foreach (var custOrd in customerOrders)
Console.WriteLine(
$"Customer: {custOrd.Customer}, Item: {custOrd.Item}");
代码清单 105
在from
子句之后,您可以使用一个或多个join
子句来访问您需要的类型。on
关键字允许您指定要在表之间匹配的键。本示例在匿名类型上创建一个投影,以创建基于连接信息的报表。这是一个普通的连接,省略了任何没有匹配的Order
的Customers
。下面的示例允许您执行相当于左连接的操作。
var customerOrders2 =
from cust in Company.Customers
join ord in Company.Orders.DefaultIfEmpty()
on cust.ID equals ord.CustomerID
select new
{
ID = cust.ID,
Customer = cust.Name,
Item = ord.Description
};
foreach (var custOrd2 in customerOrders)
Console.WriteLine(
$"Customer: {custOrd2.Customer}, Item: {custOrd2.Item}");
代码清单 106
这里的不同之处在于对DefaultIfEmpty
的调用,它包括带有Name
Gary
的Customer
,尽管在连接中没有任何订单与他的ID
匹配。
您已经看到了基本的 LINQ 语法,但是还有更多标准查询操作符。标准查询操作符实际上有几十个,你可以在 MSDN 上https://msdn . Microsoft . com/en-us/library/vstudio/bb 397896(v = vs . 120)上查看全部。aspx 。
下面的代码清单是一个示例包,演示了如何使用您可能会发现有用的标准查询运算符。
到目前为止,您一直在使用IEnumerable<T>
,其中T
是查询的预计类型。有一组标准的查询操作符会返回不同的集合类型,包括ToList
、ToArray
、ToDictionary
等等。下面是一个将结果转化为List
的例子。
var custList =
(from cust in Company.Customers
select cust)
.ToList();
custList.ForEach(cust => Console.WriteLine(cust.Name));
代码清单 107
前面的代码将查询括在括号中,然后调用ToList
运算符。List<T>
上的ForEach
方法让你通过一个λ。
LINQ 查询使用延迟执行。这意味着直到您执行一个foreach
循环或调用一个请求数据的标准查询操作符(如ToList
)时,查询才会执行。
您已经看到了 C# select
、where
、orderby
和join
关键词如何帮助构建查询。这些查询中的每一个都有一个等效的标准查询运算符。这些标准查询操作符使用流畅的语法,并以不同的方式执行与其匹配语言语法相同的查询。有些人更喜欢流畅的风格,有些人更喜欢语言语法,但你选择的方法确实是个人偏好。以下是Where
和Select
运算符的示例,它们反映了where
和select
语言语法子句。
var customers6 =
Company.Customers
.Where(cust => cust.Name.StartsWith("J"));
foreach (var cust6 in customers6)
Console.WriteLine(cust6.Name);
var customers7 =
Company.Customers.Select(cust => cust.Name);
foreach (var cust7 in customers7)
Console.WriteLine(cust7);
代码清单 108
Where
λ必须计算为一个bool
,而Select
λ可以让你指定投影。
您可以执行设置操作,如Union
、Except
和Intersect
。以下清单是Union
的一个例子。
var additionalCustomers =
new List<Customer>
{
new Customer { ID = 1, Name = "Gary" }
};
var customerUnion =
Company.Customers
.Union(additionalCustomers)
.ToArray();
foreach (var cust in customerUnion)
Console.WriteLine(cust.Name);
代码清单 109
只需传递一个兼容的集合,Union
将产生所有对象的组合集合。我在这个例子中也使用了ToArray
操作符,这导致了一个集合类型的数组,Customer
。
选择First
、FirstOrDefault
、Single
、SingleOrDefault
、Last
和LastOrDefault
有一套有用的操作符。下面的例子演示了First
。
Console.WriteLine(Company.Customers.First().Name);
代码清单 110
以这种方式使用First
的唯一一点是InvalidOperationException
有可能带有消息“序列不包含元素。”这个序列包含元素,但这不能保证。使用带OrDefault
后缀的运算符会更安全,如下列表所示。
var empty =
Company.Customers
.Where(cust => cust.ID == 999)
.SingleOrDefault();
if (empty == null)
Console.WriteLine("No values returned.");
代码清单 111
上例写"No values returned."
因为没有客户带ID == 999
,SingleOrDefault
返回null
,这是引用类型对象的默认值。
这些只是少数可用的操作符,但希望您对语言语法的丰富支持以及构成 LINQ 的标准查询操作符有所了解。
LINQ 允许您使用类似 SQL 的语法来查询数据。本章中使用的 LINQ 提供程序是“LINQ 到对象”,它允许您查询内存中的对象,但是对于其他数据源,还有许多其他 LINQ 提供程序。使用from
指定被查询的集合,使用select
对结果进行整形。where
子句允许您过滤结果,并使用bool
表达式来评估是否应该包含给定的对象。orderby
子句允许您对结果进行排序。join
子句允许您组合两个集合。标准查询操作符扩展了 LINQ,使其比语言关键字更强大。