zoukankan      html  css  js  c++  java
  • 第二十一章、使用查询表达式来查询内存中的数据

      什么是语言集成查询(LINQ)

      对从应用程序代码中查询数据的机制进行了“抽象”。这个功能称为“语言集成查询”(Language Integrated Query)。

      LINQ的设计者大量借鉴了关系数据库管理系统(例如Microsoft SQL Server)的处理方式,将“数据库查询语句”与“数据在数据库中的内部格式”分隔开。LINQ的语法和语义和SQL很像,具有许多相同的优势。要查询的数据的内部结构发生改变后,不必修改查询代码。注意,虽然LINQ和SQL看起来很像,但LINQ更加灵活,而且能处理范围更大的逻辑数据结构。

      LINQ要求数据用实现了IEnumerable或IEnumerable接口的数据结构进行存储。具体使用什么数据结构不重要。可以是数组、HashSet、Queue或者其他任何集合类型(甚至可自己定义)。唯一的要求就是这种类型是“可枚举”的。

      客户信息

      地址信息 

      假定客户和地址信息存储在如下的customers和addresses数组中。

      var customers = new[] {

      new {CustomerID = 1,FirstName = "Kim",LastName = "Abercrombie",CompanyName = "Alpine Ski House"},

      new {CustomerID = 2,FirstName = "Jeff",LastName = "Hay",CompanyName = "Coho Winery"},

      new {CustomerID = 3,FirstName = "Charlie",LastName = "Herb",CompanyName = "Alpine Ski House"},

      new {CustomerID = 4,FirstName = "Chris",LastName = "Preston",CompanyName = "Trey Research"},

      new {CustomerID = 5,FirstName = "Dave",LastName = "Barnett",CompanyName = "Wingtip Toys"},

      new {CustomerID = 6,FirstName = "Ann",LastName = "Beebe",CompanyName = "Coho Winery"},

      new {CustomerID = 7,FirstName = "John",LastName = "Kane",CompanyName = "Wingtip Toys"},

      new {CustomerID = 8,FirstName = "David",LastName = "Simpson",CompanyName = "Trey Research"},

      new {CustomerID = 9,FirstName = "Greg",LastName = "Chapman",CompanyName = "Wingtip Toys"},

      new {CustomerID = 10,FirstName = "Tim",LastName = "Litton",CompanyName = "Wide World Importers"},

      };

      var addresses = new[] {

      new {CompanyName = "Alpine Ski House", City = "Berne", Country = "Switzerland"},

      new {CompanyName = "Coho Winery", City = "San Francisco", Country = "United States"},

      new {CompanyName = "Trey Research", City = "New York", Country = "United States"},

      new {CompanyName = "Wingtip Toys", City = "Landon", Country = "United Kingdom"},

      new {CompanyName = "Wide World Importers", City = "Tetbury", Country = "United Kingdom"},

      } ;

      查询数据

      为了显示由customers数组中每个客户的名字(FirstName)组成的列表,可以写一下代码:

      IEnumerable customerFirstNames = customers.Select(cust => cust.FirstName);

      foreach(string name in customerFirstNames )

      {

      Console.WriteLine(name);

      }

      Select方法允许从数组获取特定信息。传给Select方法的参数实际上是另一个方法,该方法从customers数组中获取一行,并返回从那一行选择的数据。可用自定义的方法执行这个任务,但最简单的机制是用Lambda表达式定义匿名方法,就像上例展示的那样。目前要注意以下3个重点:

      1、cust变量是传给方法的参数。可认为cust是customers数组中的每一行的别名。

      2、Select方法目前还没开始获取数据;相反,它只是返回一个“可枚举”对象。稍后遍历它时,才会真正获取Select方法指定的数据。

      3、Select其实不是Array类型的方法。它是Enumerable类的扩展方法。Enumerable类位于System.Linq命名空间,它提供了大量静态方法来查询实现了泛型IEnumerable接口的对象。

      Select方法返回基于某具体类型的可枚举集合。如果希望枚举器返回多个数据项,例如返回每个客户的名字和姓氏,至少有以下两个方案:

      1、可以在Select方法中,将名字和姓氏连接成单独的字符串。实例如下:

      IEnumerable customerNames = customers.Select(cust => String.Format("{0}{1}",cust.FirstName,cust.LastName));

      2、可定义新类型来封装姓名和姓氏,并用Select方法构造这个类型的实例。例如:

      class FullName

      {

      public string FirstName{get;set;}

      public string LastName{get;set;}

      }

      .....

      IEnumerable customerName =  customers.Select(cust => new FullName

      {

      FirstName = cust. FirstName,

      LastName = cust.LastName

      });

      第二个选项本来应该是首选的。但如果FullName类型的作用仅限于此,就可考虑使用匿名类型,而不是专门为一个操作定义一个新类型。下面是匿名类型的例子:

      var customerName = customers.Select(cust => new{FirstName = cust. FirstName,LastName = cust.LastName});

      注意,这里使用var关键字定义可枚举的类型。集合中的对象类型是匿名的,所以不知道集合中的对象的具体类型。

      筛选数据

      Select方法允许“指定”(用更专业的术语来说,就是“投射”)想包含到可枚举集合中的字段。然而,有时希望对可枚举集合中包含的进行限制。例如,为了列出address数组中地址在美国的所有公司的名称,可以像下面这样使用Where方法。

      IEnumerable  usCompanies =

      addresses.Where(addr => String.Equals(addr.Country,"United States"))

      .Select(usComp => usComp.CompanyName);

      foreach(string name in usCompanies )

      {

      Console.WriteLine(name); //Coho Winery  Trey Research

      }

      首先应用Where方法,从而筛选出行;再应用Select方法,从而指定(或者说投射)其中特定的字段。

      排序、分组和聚合数据

      按特定顺序获取数据要使用OrderBy方法。与Select和Where方法相似,OrderBy也要求以一个方法作为实参。该方法标识了对数据进行排序的表达式

      IEnumerable  companyNames =

      addresses.OrderBy(addr => addr.CompanyName).Select(comp => comp.CompanyName);//升序

      foreach(string name in companyNames )

      {

      Console.WriteLine(name);

      }

      要求降序枚举数据,可以换用OrderByDescending方法。要按多个键排序,可以在OrderBy或OrderByDescending之后使用ThenBy或ThenByDescending。

      要按一个或多个字段中共同的值对数据进行分组,可以使用GroupBy方法。下例展示了如何按照国家对addresses数组中的公司进行分组。

      var companiesGroupedByCountry =

      addresses.GroupBy(addrs => addrs.Country);

      foreach(var companiesPerCountry in companiesGroupedByCountry )

      {

      Console.WriteLine("Country: {0} {1} companies", companiesPerCountry.Key, companiesPerCountry.Count())

      foreach(var companies in companiesPerCountry )

      {

      Console.WriteLine(" {0}", companies.CompanyName);

      }

      }

      GroupBy方法不需要同Select方法将字段投射到结果。

      可直接为Select方法的结果使用许多汇总方法,例如Count,Max和Min等。例如:

      int numberOfCompanies = addresses.Select(addr => addr.CompanyName).Count();

      Console.WriteLine("Number of companies:{0}", numberOfCompanies );

      可用Distinct方法来删除重复

      int numberOfCountries = addresses.Select(addr => addr.Country).Distinct().Count();

      Console.WriteLine("Number of countries:{0}", numberOfCountries );

      联接数据

      和SQL一样,LINQ也允许根据一个或多个匹配键(common Key)字段来联接多个数据集。下例展示了如何显示每个客户的名字和姓氏,同时显示他们所在国家的名称:

      var companiesAndCustomers = customers.Select(c => new {c.FirstName,c.LastName,c.CompanyName})

      .Join(addresses, cust =>cust.CompanyName, addrs =>addrs.CompanyName,

      (custs,addrs) => new {custs.FirstName,custs.LastName,addrs.Country });

      foreach(var row in companiesAndCustomers )

      {

      Console.WriteLine(row);

      }

      使用查询操作符

      C#的设计者为语言添加了一系列查询操作符,允许开发人员使用与SQL更相似的语法来使用LINQ功能。

      var customerFirstNames  = from cust in customers

                                                        select cust.FirstName;

      编译时,C#编译器将上述表达式解析成对应的Select方法。from操作符为来源集合定义了别名,select操作符利用该别名指定了要获取的字段。

      var customerNames  = from c in customers

                                                select new {c.FirstName,c.LastName};

      where:

      var usCompanies = from a in addresses

                                          where String.Equals(a.Country,"United States")

                                          select a.CompanyName;

      orderby:

      var companyNames = from a in address

                                               orderby a.CompanyName

                                               select a.CompanyName;

      group by:

      var companiesGoupedByCountry = from a in addresses

                                                                      group a by a.Country;

      注意,和前面用GroupBy方法对数据进行分组的例子一样,这里不需要提供select操作符,而且可以和以前一样的代码遍历结果:

      foreach(var companiesPerCountry in companiesGroupedByCountry )

      {

      Console.WriteLine("Country: {0} {1} companies", companiesPerCountry.Key, companiesPerCountry.Count())

      foreach(var companies in companiesPerCountry )

      {

      Console.WriteLine(" {0}", companies.CompanyName);

      }

      }

      可为返回的可枚举集合调用各种汇总函数,例如Count方法:

      int numberOfCompanies = (from a in addresses

                                                        select a.CompanyName).Count();

      int numberOfCountries = (from a in addresses

                                                      select a.Country).Distinct().Count();

      join:

      var citiesAndCustomers = from a in addresses

                                                      join c in customers

                                                      on a.CompanyName equals c.CompanyName

                                                      select new{c.FirstName,c.LastName,a.Country};

      LINQ和推迟求值

      使用LINQ定义可枚举集合时,不管是使用LINQ扩展方法,还是使用查询操作符,都应该记住这样一点:LINQ扩展方法执行时,应用程序不会真正构建集合;只有在遍历集合时,才会对集合进行枚举。也就是说,从执行一个LINQ查询之后,到取回这个查询所标识的数据之前,原始集合中的数据可能发生改变。但是,获取的始终是最新的数据。例如:

      var usCompanies = from a in addresses

                                         where String.Equals(a.Country,"United States")

                                         select a.CompanyName;

      除非使用以下代码遍历usCompanies 集合,否则addresses数据中数据不会获取,Where筛选器中指定的条件也不会求值:

      foreach(string name in usCompanies )

      {

      Console.WriteLine(name);

      }

      从定义usCompanies 集合到遍历这个集合,在此期间如果对addresses数组中的数据进行修改,就会看到新的数据。这个策略就是所谓的推迟求值。

      可在定义LINQ查询时强制求值,从而生成一个静态的、缓存的集合。这个集合是原始数据的拷贝。如果原始数据发生改变,这个拷贝中的数据是不会相应改变的。LINQ提供了ToList方法来构建静态List对象以包含数据的缓存拷贝。如下:

      var usCompanies = from a in addresses.ToList()

                                         where String.Equals(a.Country,"United States")

                                         select a.CompanyName;

    分组查询:

     var addresses = new[] {

                    new {EIRNo = 1, Charge = 20, CNTNo = "1"},

                    new {EIRNo = 2, Charge = 10, CNTNo = "2"},

                    new {EIRNo = 1, Charge = 5, CNTNo = "1"},

                    new {EIRNo = 2, Charge = 10, CNTNo = "2"},

                 };

                var query = from c in addresses.AsEnumerable()

                            group c by new

                            {

                                c.EIRNo,

                                c.CNTNo

                            }

                            into s

                            select new

                            {

                                EIRNo = s.Key.EIRNo,

                                CNTNo = s.Key.CNTNo,

                                Charge = s.Sum(p => p.Charge)

                            };

                foreach(var q in query)

                {

                    Console.WriteLine("EIRNO : {0} Charge : {1} CNTNo : {2}",q.CNTNo,q.Charge,q.CNTNo);

                }

    结果:EIRNO : 1 Charge : 25 CNTNo : 1
            EIRNO : 2 Charge : 20 CNTNo : 2
  • 相关阅读:
    MVC4 中Remote的使用
    NHibernate遇到的问题集 持续更新。
    2014总结,2015展望
    Redis结合EntityFramework结合使用的操作类
    Entity Framwork db First 中 Model验证解决办法。
    「面经」阿里蚂蚁金服 offer 之路
    最长公共子序列-LCS
    阿里面试题解答-倒排索引
    如何解决ubuntu下Chromium 新建的应用快捷方式图标模糊的问题
    join sleep yield
  • 原文地址:https://www.cnblogs.com/linhuide/p/5819923.html
Copyright © 2011-2022 走看看