Linq入门(二)
1. 隐式类型的局部变量
var只是为我们提供了语法上的便利,在平常使用时还是尽量使用强类型,毕竟强类型可读性较强。
/// ///<Summary>通过var声明的变量都是由编译器来推断其类型</Summary> /// //i被编译时int类型 var i = 5; //s被编译为string类型 var s = "Sunny"; //a被编译为int[]类型 var a = new[] { 0, 1, 2 }; //expr被编译为IEnumerable<Customer>类型 var expr = from c in customers where c.City == "London" select c; //anon被编译为匿名类类型 var anon = new { Name = "Sunny", Age = 22 }; //list被编译为List<int>类型 var list = new List<int>();
注意: |
---|
var关键字并不意味着“变体”,也不表示该变量是松散类型的变量或后期绑定的变量。它指示表示由编译器确定和适当分配的类型。 |
-
如上所示的局部变量(在方法中声明的局部变量)
-
在for初始化语句中
for(var x = 1; x < 10; x++)
-
在foreach初始化语句中
foreach(var item in list){...}
-
在using语句中
using (var file = new StreamReader("C:\\myfile.txt")) {...}
var和匿名类型
在使用匿名类型初始化变量时,如果需要在以后访问对象的属性,则必须将该变量声明为 var。这在 LINQ 查询表达式中很常见。匿名对象的类型名由编译器生成,并且不能在源代码中使用,它的属性类型由编译器来推断。
static void Main() { string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" }; // If a query produces a sequence of anonymous types, // then use var in the foreach statement to access the properties. var upperLowerWords = from w in words select new { Upper = w.ToUpper(), Lower = w.ToLower() }; // Execute the query foreach (var ul in upperLowerWords) { Console.WriteLine("Uppercase: {0}, Lowercase: {1}", ul.Upper, ul.Lower); } }
输出:
Uppercase: APPLE, Lowercase: apple Uppercase: BLUEBERRY, Lowercase: blueberry Uppercase: CHERRY, Lowercase: cherry
备注
下列限制适用于隐式类型的变量声明:
-
只有在同一语句中声明和初始化局部变量时,才能使用 var;不能将该变量初始化为 null、方法组或匿名函数。
-
不能将 var 用于类范围的域。
-
由 var 声明的变量不能用在初始化表达式中。换句话说,表达式 int i = (i = 20) 是合法的;但表达式 var i = (i = 20) 则会产生编译时错误。
-
不能在同一语句中初始化多个隐式类型的变量。
-
如果范围中有一个名为 var 的类型,则 var 关键字将解析为该类型名称,而不作为隐式类型局部变量声明的一部分进行处理。
2. Linq和泛型类型
LINQ 查询基于泛型类型,IEnumerable<T> 是一个接口,通过该接口,可以使用 foreach 语句来枚举泛型集合类。泛型集合类都支持 IEnumerable<T>。
Linq查询中的IEnumerable<T>变量
LINQ 查询变量类型化为 IEnumerable<T> 或派生类型,如 IQueryable<T>。当您看到类型化为 IEnumerable<Customer> 的查询变量时,这只意味着在执行该查询时,该查询将生成包含零个或多个 Customer 对象的序列。
class Program { static void Main(string[] args) { List<Customer> customers = new List<Customer>() { new Customer("Jack", "Chen", "London"), new Customer("Sunny","Peng", "Shenzhen"), new Customer("Tom","Cat","London") }; //使用IEnumerable<T>作为变量 IEnumerable<Customer> customerQuery = from cust in customers where cust.City == "London" select cust; foreach (Customer customer in customerQuery) { Console.WriteLine(customer.LastName + "," + customer.FirstName); } Console.Read(); } } public class Customer { public Customer(string firstName, string lastName, string city) { FirstName = firstName; LastName = lastName; City = city; } public string FirstName { get; set; } public string LastName { get; set; } public string City { get; set; } }
使用var局部变量,让编译器处理泛型类型声明
var customerQuery2 = from cust in customers where cust.City == "London" select cust; foreach(var customer in customerQuery2) { Console.WriteLine(customer.LastName + ", " + customer.FirstName); }
3. Linq的基本查询操作
Linq的基本查询操作包括以下几个:
- 获取数据源
- 筛选
- 排序
- 分组
- 联接
- 选择
class Program { public static List<Customer> GetCustomers() { List<Customer> customers = new List<Customer> { new Customer{Name="Sunny Peng", City="ShenZhen"}, new Customer{Name="Devon", City="London"}, new Customer{Name="JayZ", City="Paris"}, new Customer{Name="John Smith", City="London"}, new Customer{Name="Jerry Chou", City="Paris"}, }; return customers; } static void Main(string[] args) { } } public class Customer { public string Name { get; set; } public string City { get; set; } }
说明: |
---|
下面的几个和Customer相关的例子中使用的数据源都是如上所示。 |
获取数据源
在Linq的查询中,第一步是指定数据源,通过from… in…子句引入范围变量和数据源。
//cust是范围变量,customers是数据源 var queryAllCustomers = from cust in customers select cust;
筛选
筛选就是指定条件,排除一些结果,指定条件使用where子句。
下面列举了几个简单的筛选例子:
返回地址位于伦敦的customers
var queryLondonCustomers = from cust in customers where cust.City == "London" select cust;
可以使用熟悉的 C# 逻辑 AND 和 OR 运算符来根据需要在 where 子句中应用任意数量的筛选表达式。例如,若要只返回位于“伦敦”AND 姓名为“Devon”的客户,您应编写下面的代码:
where cust.City=="London" && cust.Name == "Devon"
若要返回位于伦敦或巴黎的客户,应编写下面的代码:
where cust.City == "London" || cust.City == "Paris"
在where中使用bool方法,这种方式显得更为灵活
static void Main() { //数据源 int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; //在where子句中调用bool方法 var queryEvenNums = from num in numbers where IsEven(num) select num; // Execute the query. foreach (var s in queryEvenNums) { Console.Write(s.ToString() + " "); } } static bool IsEven(int i) { return i % 2 == 0; }
where 子句是一种筛选机制。除了不能是第一个或最后一个子句外,它几乎可以放在查询表达式中的任何位置。where 子句可以出现在 group 子句的前面或后面,具体情况取决于是必须在对源元素进行分组之前还是之后来筛选源元素。
如果指定的谓词对于数据源中的元素无效,则会发生编译时错误。这是 LINQ 提供的强类型检查的一个优点。
编译时,where 关键字会被转换为对 Where 标准查询运算符方法的调用。
排序
如果查询结果顺序比较混乱,我们可以通过使用orderby子句对结果进行排序。orderby 子句将使返回的序列中的元素按照被排序的类型的默认比较器进行排序。例如,下面的查询可以扩展为按 Name 属性对结果进行排序。因为 Name 是一个字符串,所以默认比较器执行从 A 到 Z 的字母排序。
var queryLondonCustomers3 = from cust in customers where cust.City == "London" orderby cust.Name ascending select cust;
若要按相反顺序(从 Z 到 A)对结果进行排序,则使用 orderby…descending 子句。
看下面这个例子,同时使用了Ascending和Descending对水果进行排序:
static void Main(string[] args) { //数据源:樱桃,苹果,南方越橘 string[] fruits = {"cherry", "apple", "blueberry" }; //以升序方式对查询进行排序,默认排序方式是升序 IEnumerable<string> sortAscendingQuery = from fruit in fruits orderby fruit select fruit; IEnumerable<string> sortDescendingQuery = from w in fruits orderby w descending select w; //执行查询 Console.WriteLine("Ascending:"); foreach (string s in sortAscendingQuery) { Console.WriteLine(s); } //执行查询 Console.WriteLine("Descending:"); foreach (string s in sortDescendingQuery) { Console.WriteLine(s); } Console.Read(); }
下面的示例对学生的姓氏执行主要排序,然后对他们的名字执行次要排序:
namespace LinqGrammer { public class Student { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } class Program { public static List<Student> GetStudents() { List<Student> students = new List<Student> { new Student {FirstName="Svetlana", LastName="Omelchenko", ID=111}, new Student {FirstName="Claire", LastName="O'Donnell", ID=112}, new Student {FirstName="Sven", LastName="Mortensen", ID=113}, new Student {FirstName="Cesar", LastName="Garcia", ID=114}, new Student {FirstName="Debra", LastName="Garcia", ID=115} }; return students; } static void Main(string[] args) { //创建数据源 List<Student> students = GetStudents(); //创建查询 IEnumerable<Student> sortedStudents = from student in students orderby student.LastName ascending, student.FirstName ascending select student; //执行查询 Console.WriteLine("Sorted Students:"); foreach (Student student in sortedStudents) { Console.WriteLine(student.LastName + " " + student.FirstName); } //第一个orderby,以姓名进行升序排序 //group by以LastName的首字母进行分组 //第二个orderby,分组后再进行排序 var sortedGroups = from student in students orderby student.LastName, student.FirstName group student by student.LastName[0] into newGroup orderby newGroup.Key select newGroup; Console.WriteLine("\nSorted Groups:"); foreach (var studentGroup in sortedGroups) { Console.WriteLine(studentGroup.Key); foreach (var student in studentGroup) { Console.WriteLine(" {0}, {1}", student.LastName, student.FirstName); } } Console.Read(); } } }
在上面那一段稍微复杂一点的查询表达式中,
对数据源students以LastName, FirstName进行排序,
然后再以LastName的首字母对student(student是范围变量)进行进行分组,LastName的首字母是newGroup的Key,分组以后相当于有多个结果集,
然后再对组进行排序,
最终我们看到的结果集是二维的,第一维是newGroup.Key,第二维是根据newGroup.Key分组得到的结果集。
输出结果:
Sorted Students:
Garcia Cesar
Garcia Debra
Mortensen Sven
O'Donnell Claire
Omelchenko Svetlana
Sorted Groups:
G
Garcia, Cesar
Garcia, Debra
M
Mortensen, Sven
O
O'Donnell, Claire
Omelchenko, Svetlana
分组
使用group子句,我们可以按指定的键(Key)对结果进行分组。如下面一个例子,对客户以City进行分组,cust.City就是键。
//数据源 List<Customer> customers = GetCustomers(); //queryCustomerByCity的类型是IEnumerable<IGrouping<string,Customer>>类型 var queryCustomerByCity = from cust in customers group cust by cust.City; //customerGroup的类型是IGrouping<string,Customer>类型 foreach (var customerGroup in queryCustomerByCity) { Console.WriteLine("\nGroup Key:" + customerGroup.Key); foreach (Customer customer in customerGroup) { Console.WriteLine("{0}", customer.Name); } } Console.Read();
在刚才的那个Student的分组例子中,我们已经知道进行分组后的结果是二维的,而在上面这个Customer例子中我们偷懒使用了var声明隐式类型变量。
这里queryCustomerByCity的类型有点复杂,它是<IGrouping<string,Customer>>类型,在foreach中customerGroup变量是IGrouping<string,Customer>类型
如果用强类型来声明这些变量,应该是这样:
List<Customer> customers = GetCustomers(); IEnumerable<IGrouping<string, Customer>> queryCustomerByCity = from cust in customers group cust by cust.City; foreach (IGrouping<string, Customer> customerGroup in queryCustomerByCity) { Console.WriteLine("\nGroup Key:" + customerGroup.Key); foreach (Customer customer in customerGroup) { Console.WriteLine("{0}", customer.Name); } } Console.Read();
这时var的好处就显而易见了,当变量类型比较复杂时,使用var能减少不少麻烦。
下面再看几个比较复杂的分组例子。
按bool值进行分组
class Program { public class Student { public string First { get; set; } public string Last { get; set; } public int ID { get; set; } public List<int> Scores; } public static List<Student> GetStudents() { List<Student> students = new List<Student> { new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}}, new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}}, new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}}, new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}}, new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}} }; return students; } static void Main(string[] args) { //数据源 List<Student> students = GetStudents(); //以平均分在80以上和以下进行分组 var booleanGroupQuery = from student in students group student by student.Scores.Average() >= 80; foreach (var studentGroup in booleanGroupQuery) { Console.WriteLine(studentGroup.Key == true ? "High averages" : "Low averages"); foreach (var student in studentGroup) { //输出姓名,平局分,默认排序是升序,先输出Low averages组 Console.WriteLine("{0},{1}:{2}",student.Last, student.First, student.Scores.Average()); } } Console.Read(); } }
输出:
Low averages Omelchenko, Svetlana:77.5 O'Donnell, Claire:72.25 Garcia, Cesar:75.5 High averages Mortensen, Sven:93.5 Garcia, Debra:88.25
按数值范围分组
下面这个示例是以学生的平均分进行分组,并且以每10分为一组,平均分为50~60的为一组,平均分为60~70的为一组,以此类推。
namespace LinqGrammer { class Program { public class Student { public string First { get; set; } public string Last { get; set; } public int ID { get; set; } public List<int> Scores; } public static List<Student> GetStudents() { List<Student> students = new List<Student> { new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}}, new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}}, new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}}, new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}}, new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}} }; return students; } static void Main(string[] args) { //数据源 List<Student> students = GetStudents(); //以平均分每10分为一组,比如60~70是一组,70~80是一组,以此类推 var studentQuery = from student in students let avg = (int)student.Scores.Average() group student by (avg == 0 ? 0 : avg / 10) into g orderby g.Key select g; //执行查询 foreach (var studentGroup in studentQuery) { int temp = studentGroup.Key * 10; Console.WriteLine("Students with an average between {0} and {1}", temp, temp + 10); foreach (var student in studentGroup) { Console.WriteLine(" {0},{1}:{2}", student.Last, student.First, student.Scores.Average()); } } Console.Read(); } } }
注意上面的查询语句中使用到了let关键字,let可以用于存储字表达式的结果,它创建一个新的范围变量,并能够在随后的表达式中使用到这个变量。但是一旦初始化了该范围变量,它就不能用于存储其他值。
输出:
Students with an average between 70 and 80 Omelchenko, Svetlana:77.5 O'Donnell, Claire:72.25 Garcia, Cesar:75.5 Students with an average between 80 and 90 Garcia, Debra:88.25 Students with an average between 90 and 100 Mortensen, Sven:93.5
按复合键分组
当需要按照多个键对元素进行分组时,可以使用复合键。通过使用匿名类型或命名类型来存储元素,可以创建复合键。假定已经使用名为 surname 和 city 的两个成员声明了类 Person。group 子句使得为每组具有相同姓氏和相同城市的人员创建一个单独的组。
group person by new {name = person.surname, city = person.city};
group分组小总结
Group分组简单总结为三点:
- 基本规则:group 范围变量 by 条件 into 组名
- 查询变量的类型为IEnumerable<IGrouping<TKey, TElement>>类型
- 执行查询时使用两层foreach进行遍历,外层foreach遍历Key,一个Key代表一组;内层foreach遍历每一组中的元素
- 外层foreach的变量类型为IGrouping<TKey, TElement>类型,内层foreach类型为TElement
联接
常见的联接包含三种类型
- 内部联接
- 分组联接
- 左外部联接
内部联接
(1)简单内联接
使用内联接的基本规则:
from … in datasource1
join … in datasoruce2
on conditiion
class Product { public int ID { get; set; } public string Name { get; set; } public int CategoryID { get; set;} } class Category { public int ID { get; set; } public string Name { get; set; } } class Program { static void Main(string[] args) { Product chai = new Product { ID = 1, Name = "Chai", CategoryID = 1 }; Product chang = new Product { ID = 2, Name = "Chang", CategoryID = 1 }; Product aniseed = new Product { ID = 3, Name = "Aniseed Syrup", CategoryID = 2 }; Product teattime = new Product { ID = 4, Name = "Teatime", CategoryID = 3 }; Product nul = new Product { ID = 5, Name = "Nulce", CategoryID = 5 }; Category beverages = new Category { ID = 1, Name = "Beverages" }; Category condiments = new Category { ID = 2, Name = "Condiments" }; Category confections = new Category { ID = 3, Name = "Confections" }; List<Product> products = new List<Product> { chai, chang, aniseed, teattime, nul }; List<Category> categories = new List<Category> { beverages, condiments, confections }; //使用内联接 //基本规则: from ... in datasource1 // join ... in datesource2 // on condition //最后select的时候创建了一系列的匿名对象 var query = from product in products join category in categories on product.CategoryID equals category.ID select new { ProductName = product.Name, CategoryName = category.Name }; //执行查询 //注意nul不在输出队列中 foreach (var validProduct in query) { Console.WriteLine("Product \"{0}\" is in Category of {1}", validProduct.ProductName, validProduct.CategoryName); } Console.Read(); } }
(2)复合内联接
采用复合键、内联接的方式查询,复合键的声明使用匿名类型。
class Employee { public string FirstName { get; set; } public string LastName { get; set; } public int EmployeeID { get; set; } } class Student { public string FirstName { get; set; } public string LastName { get; set; } public int StudentID { get; set; } } class Program { /// <summary> /// 查询既是Employee又是Student的人 /// </summary> public static void CompositeKeyJoinExample() { List<Employee> employees = new List<Employee> { new Employee { FirstName = "Terry", LastName = "Adams", EmployeeID = 522459 }, new Employee { FirstName = "Charlotte", LastName = "Weiss", EmployeeID = 204467 }, new Employee { FirstName = "Magnus", LastName = "Hedland", EmployeeID = 866200 }, new Employee { FirstName = "Vernette", LastName = "Price", EmployeeID = 437139 } }; List<Student> students = new List<Student> { new Student { FirstName = "Vernette", LastName = "Price", StudentID = 9562 }, new Student { FirstName = "Terry", LastName = "Earls", StudentID = 9870 }, new Student { FirstName = "Terry", LastName = "Adams", StudentID = 9913 } }; //复合内联接,使用匿名类型作为复合键 IEnumerable<string> query = from employee in employees join student in students on new { employee.FirstName, employee.LastName } equals new { student.FirstName, student.LastName } select employee.FirstName + " " + employee.LastName; Console.WriteLine("The following people are both employees and students:"); foreach (string name in query) { Console.WriteLine(name); } } static void Main(string[] args) { CompositeKeyJoinExample(); Console.Read(); } }
(3)多联接
多联接就意味着至少有3个数据源,至少两个join,就像SQL中 n ( n>=3 )表联接查询一样。
只要掌握了简单联接的方式,拿下多联接也是轻而易举。
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } class Cat : Pet { } class Dog : Pet { } class Program { /// <summary> /// 多联接的使用 /// </summary> public static void MutipleJoinExample() { Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; Person rui = new Person { FirstName = "Rui", LastName = "Raposo" }; Person phyllis = new Person { FirstName = "Phyllis", LastName = "Harris" }; Cat barley = new Cat { Name = "Barley", Owner = terry }; Cat boots = new Cat { Name = "Boots", Owner = terry }; Cat whiskers = new Cat { Name = "Whiskers", Owner = charlotte }; Cat bluemoon = new Cat { Name = "Blue Moon", Owner = rui }; Cat daisy = new Cat { Name = "Daisy", Owner = magnus }; Dog fourwheeldrive = new Dog { Name = "Four Wheel Drive", Owner = phyllis }; Dog duke = new Dog { Name = "Duke", Owner = magnus }; Dog denim = new Dog { Name = "Denim", Owner = terry }; Dog wiley = new Dog { Name = "Wiley", Owner = charlotte }; Dog snoopy = new Dog { Name = "Snoopy", Owner = rui }; Dog snickers = new Dog { Name = "Snickers", Owner = arlene }; List<Person> people = new List<Person> { magnus, terry, charlotte, arlene, rui, phyllis }; List<Cat> cats = new List<Cat> { barley, boots, whiskers, bluemoon, daisy }; List<Dog> dogs = new List<Dog> { fourwheeldrive, duke, denim, wiley, snoopy, snickers }; //第一个join匹配cat.Owner和Person,没有猫的Person将被排除掉 //第二个join匹配dog.Owner和cat.Own,即猫和狗具有相同一个主人,并且猫和狗的首字母相同 var query = from person in people join cat in cats on person equals cat.Owner join dog in dogs on new { Owner = person, Letter = cat.Name.Substring(0, 1) } equals new { dog.Owner, Letter = dog.Name.Substring(0, 1) } select new { OwnerName = person.FirstName + " " + person.LastName,CatName = cat.Name, DogName = dog.Name }; Console.WriteLine("The below cat and dog share an owner, and their first letter are the same:"); foreach(var obj in query) { Console.WriteLine("Owner Name:{0}, Cat Name:{1}, Dog Name:{2}", obj.OwnerName, obj.CatName, obj.DogName); } } static void Main(string[] args) { MutipleJoinExample(); Console.Read(); } }
(4)使用分组联接实现内联接示例
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } class Program { public static void InnerGroupJoinExample() { Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; Pet barley = new Pet { Name = "Barley", Owner = terry }; Pet boots = new Pet { Name = "Boots", Owner = terry }; Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry }; Pet daisy = new Pet { Name = "Daisy", Owner = magnus }; List<Person> people = new List<Person> { magnus, terry, charlotte, arlene }; List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy }; //以Person进行分组,分别查询每个Person所拥有的Pet //在join in后用into进行分组 //第二个from以分组后的数据作为新的数据源 var query1 = from person in people join pet in pets on person equals pet.Owner into gj from subpet in gj select new { OwnerName = person.FirstName, PetName = subpet.Name }; Console.WriteLine("Inner join using GroupJoin():"); foreach (var v in query1) { Console.WriteLine("{0} - {1}", v.OwnerName, v.PetName); } } static void Main(string[] args) { InnerGroupJoinExample(); Console.Read(); } }
分组联接
在上面的内联接最后一个示例中,我们已经使用到了分组联接,分组联接查询和普通的分组查询有所不同。
1. 普通的分组查询规则:group 范围变量 by 条件 into 组名
2. 分组联接查询规则:
from 范围变量1 in 数据源1
join 范围变量2 in 数据源2
on 联接条件
into 组名
第一种情况下的分组是有Key属性的,第二种情况下的分组是没有Key属性的。从这里就可以看出,只有使用group… by…以后的组才有Key属性。
上面的那个分组联接示例也可以通过Linq to XML很容易的转换为XML格式输出,如此使用你会觉得Linq查询非常灵活。
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } class Program { public static void InnerGroupJoinExample() { Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; Pet barley = new Pet { Name = "Barley", Owner = terry }; Pet boots = new Pet { Name = "Boots", Owner = terry }; Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry }; Pet daisy = new Pet { Name = "Daisy", Owner = magnus }; List<Person> people = new List<Person> { magnus, terry, charlotte, arlene }; List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy }; //创建XML元素,使用XElement要引入System.Xml.Linq命名空间 XElement ownerAdnPets = new XElement("PetOwners", from person in people join pet in pets on person equals pet.Owner into gj select new XElement("Person", new XAttribute("FirstName", person.FirstName), new XAttribute("LastName", person.LastName), from subpet in gj select new XElement("Pet", subpet.Name))); Console.WriteLine(ownerAdnPets); } static void Main(string[] args) { InnerGroupJoinExample(); Console.Read(); } }
输出:
<PetOwners> <Person FirstName="Magnus" LastName="Hedlund"> <Pet>Daisy</Pet> </Person> <Person FirstName="Terry" LastName="Adams"> <Pet>Barley</Pet> <Pet>Boots</Pet> <Pet>Blue Moon</Pet> </Person> <Person FirstName="Charlotte" LastName="Weiss"> <Pet>Whiskers</Pet> </Person> <Person FirstName="Arlene" LastName="Huff" /> </PetOwners>
左外联接
左外部联接就是在其中返回第一个集合的每个元素,而无论该元素在第二个集合中是否具有相关元素。
生成两个集合的做外连接的基本步骤:
1. 使用分组联接执行内部联接
2. 在结果集内包含第一个(左)集合的每个元素,即使该元素在右集合中没有匹配的元素也是如此。实现左外联接的关键是通过对分组联接中的每个匹配元素序列调用 DefaultIfEmpty。
在此示例中,对每个匹配的 Pet 对象序列调用 DefaultIfEmpty。如果对于任何 Person 对象,匹配的 Pet 对象序列都为空,则该方法将返回一个包含单个默认值的集合,从而确保每个 Person 对象都在结果集合中得到表示。
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } class Program { /// <summary> /// 左外联接示例 /// </summary> public static void LeftOuterJoinExample() { Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; Pet barley = new Pet { Name = "Barley", Owner = terry }; Pet boots = new Pet { Name = "Boots", Owner = terry }; Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry }; Pet daisy = new Pet { Name = "Daisy", Owner = magnus }; List<Person> people = new List<Person> { magnus, terry, charlotte, arlene }; List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy }; //1.分组内联接 //2.调用DefaultIfEmpty() //需要注意的是:在这里gj的类型是IEnumerable<Pet>,IEnumerable<Pet>集合分组的依据是person == pet.Owner //当分到某一组(即某个Person对象),不存在Pet对象时,调用DefaultIfEmpty()将返回一个包含单个默认值的集合 //所以最终没有Pet的Person对象也将出现在查询结果集中 var query = from person in people join pet in pets on person equals pet.Owner into gj from subpet in gj.DefaultIfEmpty() select new { person.FirstName, PetName = (subpet == null) ? String.Empty : subpet.Name }; foreach (var v in query) { Console.WriteLine("{0,-15}{1}", v.FirstName + ":", v.PetName); } } static void Main(string[] args) { LeftOuterJoinExample(); Console.Read(); } }
输出:
Magnus: Daisy Terry: Barley Terry: Boots Terry: Blue Moon Charlotte: Whiskers Arlene:
选择(投影)
select 子句生成查询结果并指定每个返回的元素的“形状”或类型。例如,可以指定结果包含的是整个 Customer 对象、仅一个成员、成员的子集,还是某个基于计算或新对象创建的完全不同的结果类型。当 select 子句生成除源元素副本以外的内容时,该操作称为“投影”。使用投影转换数据是 LINQ 查询表达式的一种强大功能。
使用Linq进行数据转换
Linq不仅可以用于检索数据,而且还是一个功能非常强大的数据转换工具。通过Linq查询,可以将原序列用作输入,并采用多种方式修改它以创建新的输出序列。可以通过排序和分组来修改序列本身,而不必修改许愿元素本身。但是,LINQ 查询最强大的功能可能在于它能够创建新类型。这一功能在select子句中实现。它能够执行以下任务:
-
将多个输入序列合并到具有新类型的单个输出序列中。
-
创建其元素只包含源序列中的各个元素的一个或几个属性的输出序列。
-
创建其元素包含对源数据执行的操作结果的输出序列。
-
创建不同格式的输出序列。例如,您可以将 SQL 行或文本文件的数据转换为 XML。
(1)将多个输入联接到一个输出序列
可以使用 LINQ 查询来创建包含多个输入序列的元素的输出序列。下面的示例演示如何组合两个内存中的数据结构,但组合来自 XML 或 SQL 或数据集源的数据时可应用相同的原则。
public class Student { public string First { get; set; } public string Last { get; set; } public int ID { get; set; } public string Street { get; set; } public string City { get; set; } public List<int> Scores; } public class Teacher { public string First { get; set; } public string Last { get; set; } public int ID { get; set; } public string City { get; set; } } class Program { /// <summary> /// 查询在西雅图的人,不管他的身份是学生还是老师 /// </summary> /// <param name="args"></param> static void Main(string[] args) { // Create the first data source. List<Student> students = new List<Student>() { new Student {First="Svetlana", Last="Omelchenko", ID=111, Street="123 Main Street", City="Seattle", Scores= new List<int> {97, 92, 81, 60}}, new Student {First="Claire", Last="O’Donnell", ID=112, Street="124 Main Street", City="Redmond", Scores= new List<int> {75, 84, 91, 39}}, new Student {First="Sven", Last="Mortensen", ID=113, Street="125 Main Street", City="Lake City", Scores= new List<int> {88, 94, 65, 91}}, }; // Create the second data source. List<Teacher> teachers = new List<Teacher>() { new Teacher {First="Ann", Last="Beebe", ID=945, City = "Seattle"}, new Teacher {First="Alex", Last="Robinson", ID=956, City = "Redmond"}, new Teacher {First="Michiyo", Last="Sato", ID=972, City = "Tacoma"} }; // Create the query var peropleInSeattle = (from student in students where student.City == "Seattle" select student.Last) .Concat(from teacher in teachers//把多个输入联接到一个输出序列 where teacher.City == "Seattle" select teacher.Last); Console.WriteLine("The following students and teachers live in Seattle:"); foreach (var person in peropleInSeattle) { Console.WriteLine(person); } Console.Read(); } }
(2)将内存中的对象转换为XML
通过 LINQ 查询,可以轻松地在内存中的数据结构、SQL 数据库、ADO.NET 数据集和 XML 流或文档之间转换数据。下面的示例将内存中的数据结构中的对象转换为 XML 元素。
class Student { public string First { get; set; } public string Last { get; set; } public int ID { get; set; } public List<int> Scores; } class XMLTransfer { static void Main(string[] args) { List<Student> students = new List<Student>() { new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores = new List<int>{97, 92, 81, 60}}, new Student {First="Claire", Last="O’Donnell", ID=112, Scores = new List<int>{75, 84, 91, 39}}, new Student {First="Sven", Last="Mortensen", ID=113, Scores = new List<int>{88, 94, 65, 91}}, }; //创建查询 var studentsToXML = new XElement("Root", from student in students let x = String.Format("{0},{1},{2},{3}",//使用范围变量存储学生的分数 student.Scores[0], student.Scores[1], student.Scores[2], student.Scores[3]) select new XElement("student", new XElement("First", student.First), new XElement("Last", student.Last), new XElement("Scores", x) ) ); Console.WriteLine(studentsToXML); Console.Read(); } }
输出:
<Root> <student> <First>Svetlana</First> <Last>Omelchenko</Last> <Scores>97,92,81,60</Scores> </student> <student> <First>Claire</First> <Last>O'Donnell</Last> <Scores>75,84,91,39</Scores> </student> <student> <First>Sven</First> <Last>Mortensen</Last> <Scores>88,94,65,91</Scores> </student> </Root>