Skip to content

Files

Latest commit

c70125a · Jan 8, 2022

History

History
348 lines (237 loc) · 14.4 KB

7.md

File metadata and controls

348 lines (237 loc) · 14.4 KB

七、使用 LINQ 查询对象

语言集成查询(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 语法都保持不变。

查询集合

查询数据只需要fromselect两个关键词。记得为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,这将列表过滤到GaryJennifer&&运算符右侧的子句过滤列表,甚至进一步过滤第一个字符不是"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关键字允许您指定要在表之间匹配的键。本示例在匿名类型上创建一个投影,以创建基于连接信息的报表。这是一个普通的连接,省略了任何没有匹配的OrderCustomers。下面的示例允许您执行相当于左连接的操作。

            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 GaryCustomer,尽管在连接中没有任何订单与他的ID匹配。

使用标准运算符

您已经看到了基本的 LINQ 语法,但是还有更多标准查询操作符。标准查询操作符实际上有几十个,你可以在 MSDN 上https://msdn . Microsoft . com/en-us/library/vstudio/bb 397896(v = vs . 120)上查看全部。aspx

下面的代码清单是一个示例包,演示了如何使用您可能会发现有用的标准查询运算符。

到目前为止,您一直在使用IEnumerable<T>,其中T是查询的预计类型。有一组标准的查询操作符会返回不同的集合类型,包括ToListToArrayToDictionary等等。下面是一个将结果转化为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# selectwhereorderbyjoin关键词如何帮助构建查询。这些查询中的每一个都有一个等效的标准查询运算符。这些标准查询操作符使用流畅的语法,并以不同的方式执行与其匹配语言语法相同的查询。有些人更喜欢流畅的风格,有些人更喜欢语言语法,但你选择的方法确实是个人偏好。以下是WhereSelect运算符的示例,它们反映了whereselect语言语法子句。

            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λ可以让你指定投影。

您可以执行设置操作,如UnionExceptIntersect。以下清单是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

选择FirstFirstOrDefaultSingleSingleOrDefaultLastLastOrDefault有一套有用的操作符。下面的例子演示了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 == 999SingleOrDefault返回null,这是引用类型对象的默认值。

这些只是少数可用的操作符,但希望您对语言语法的丰富支持以及构成 LINQ 的标准查询操作符有所了解。

总结

LINQ 允许您使用类似 SQL 的语法来查询数据。本章中使用的 LINQ 提供程序是“LINQ 到对象”,它允许您查询内存中的对象,但是对于其他数据源,还有许多其他 LINQ 提供程序。使用from指定被查询的集合,使用select对结果进行整形。where子句允许您过滤结果,并使用bool表达式来评估是否应该包含给定的对象。orderby子句允许您对结果进行排序。join子句允许您组合两个集合。标准查询操作符扩展了 LINQ,使其比语言关键字更强大。