第二十一章使用查询表达式来查询内存中的数据
Posted 很厉害的学渣
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第二十一章使用查询表达式来查询内存中的数据相关的知识,希望对你有一定的参考价值。
什么是语言集成查询(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}\\t{1} companies", companiesPerCountry.Key, companiesPerCountry.Count())
foreach(var companies in companiesPerCountry )
{
Console.WriteLine("\\t{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}\\t{1} companies", companiesPerCountry.Key, companiesPerCountry.Count())
foreach(var companies in companiesPerCountry )
{
Console.WriteLine("\\t{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);
}
以上是关于第二十一章使用查询表达式来查询内存中的数据的主要内容,如果未能解决你的问题,请参考以下文章