「C#」LinQ查询表达式

关于LinQ查询表达式

语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# 语言的技术统称。

LINQ 通过提供处理各种数据源和数据格式的数据的一致模型,简化了每种数据源或数据格式再查询时使用不同查询语言的情况。

LINQ 查询操作都由以下三个不同的操作组成:

  1. 获取数据源:
      支持 IEnumerable 或派生接口(如泛型 IQueryable)的类型称为可查询类型
  2. 创建查询:
      查询指定要从数据源中检索的信息。 查询还可以指定在返回这些信息之前如何对其进行排序、分组和结构化。from 子句指定数据源,where 子句应用筛选器,select 子句指定返回的元素的类型
  3. 执行查询:
      查询变量承载的时查询命令本身。查询的实际执行是推迟到查询变量的使用时进行的,例如在 foreach 语句中循环访问查询变量时。
      对一系列源元素执行聚合函数例如:Count、Max、Average、First 等,由于本身内部使用foreach以回结果,因此这些函数嗲用时会立即执行查询。 还要注意,这些类型的查询返回单个值,而不是集合。
      也可使用 ToList() 或 ToArray()来立即执行并将数据缓存到内存中
List<int> numQuery2 = (from num in numbers
                       where (num % 2) == 0
                       select num).ToList();

基本LinQ操作

基础语法

from

  from就是获取数据源,其语法是from xx in xxs。from后面跟的时范围变量,in后面就是数据源。

where

  where 是筛选的关键字,其后是筛选条件。可以使用“&&”或“||”等添加很多条件。
例如:

var queryLondonCustomers = from cust in customers
                           where cust.City == "London"
                           select cust;

  where子句中,除了是一系列的判断外,也可以是返回bool值的方法。即当筛选较复杂时,我们可以先声明一个用于判断每个元素是否符合要求的函数,在where子句中调用该函数。
  一次查询中(一个from下),也可以有多个where子句,来实现多次筛选。例如:

var queryLowNums3 = from num in numbers
    				where num < 5
   					where num % 2 == 0
   					select num;

orderby

  orderby对返回的数据进行排序。有两种的排序:升序和降序,默认的为升序,即数据逐渐增大,或者字母从a到z。默认升序:ascending,可以不写。
降序:descending。
语法例:

// Query for ascending sort.
IEnumerable<string>sortAscendingQuery =
    from fruit in fruits
    orderby fruit //"ascending" is default
    select fruit;
// Query for descending sort.
IEnumerable<string>sortDescendingQuery =
    from w in fruits
    orderby w descending
    select w;

  次要排序:orderby中可以填写多个排序选项,用逗号隔开,后面的成为次要排序。例如:

IEnumerable<string> query = from word in words  
                            orderby word.Length, word.Substring(0, 1)  
                            select word;  

group

  group即根据所指定的键进行分组。使用 group 子句的查询结果是一个 IGrouping 对象序列,其中的每个元素都是具有 TKey 成员的对象的集合(一组)。即:在循环访问该查询结果,必须使用嵌套 foreach 循环, 外层循环循环访问每个组,内层循环循环访问每个组的成员。例如:

// queryCustomersByCity 类型实际上是 IEnumerable>
//上面的string类型的来源是cust.City的类型。
  var queryCustomersByCity = from cust in customers
                             group cust by cust.City;

  // customerGroup is an IGrouping
  foreach (var customerGroup in queryCustomersByCity)
  {
      Console.WriteLine(customerGroup.Key);
      foreach (Customer customer in customerGroup)
      {
          Console.WriteLine("    {0}", customer.Name);
      }
  }

  分组条件默认是与给定的键值相等,也可以自定义其他条件,即一个以bool类型为返回值的语句,也可以对范围变量进行处理以获得新的键值。
例如(将学生成绩按10分为一档次进行分组):

var studentQuery = from student in students
                   let avg = (int)student.Scores.Average()
                   group student by (avg / 10) into g
                   orderby g.Key
                   select g;

select

  select 子句生成查询结果并指定每个返回的元素的“形状”或类型。乘称作“选择”或“投影”。换言之,即前面所有的操作之后将数据拆散分组排序后,select即从中挑选所需要的数据并组成需要的类型。例如可以是范围变量或分组后的便变量本身。亦可使用这些变量构造新的类型例如字典、匿名类、其他类,或者是元素的某个字段,或者字段值做一定处理后的值。

//导出每个学生的平均成绩
IEnumerable<double> studentQuery6 = from student in app.students
                                    where student.ID > 111
                                    select student.Scores.Average();

//以学生first和last构成新匿名类的集合
var studentQuery7 = from student in app.students
                    where student.ID > 111
                    select new { student.First, student.Last };

//用学生平均成绩和Id构造ScoreInfo对象并填入集合
IEnumerable<ScoreInfo> studentQuery8 = from student in app.students
                                       where student.ID > 111
                                       select new ScoreInfo
                                       {
                                           Average = student.Scores.Average(),
                                           ID = student.ID
                                       };
                                       
//找到学生平均成绩大于85的,并在联系列表中更具Id找到联系方式
IEnumerable<ContactInfo> studentQuery9 = from student in app.students
                                         where student.Scores.Average() > 85
                                         join ci in app.contactList on student.ID equals ci.ID
                                         select ci;

进阶语法

into

  可使用 into 创建临时标识符,将 group、join 或 select 子句的结果存储至新标识符。into创建的新标识本身可以继续附加查询命令。 有时称在 group 或 select 子句中使用新标识符为“延续”。

let

  该关键字创建一个新的范围变量并通过提供的表达式结果初始化该变量。简单的说,类似于英文中的“set”,即引入一个新的变量,来代替一些操作。例如前文中group关键字的例子中的那样。
再举一个例子(从一段话中挑出元音开头的单词):

string[] strings =
{
    "A penny saved is a penny earned.",
    "The early bird catches the worm.",
    "The pen is mightier than the sword."
};
// Split the sentence into anarray of words
// and select those whose firstletter is a vowel.
var earlyBirdQuery =
    from sentence in strings
    let words = sentence.Split(' ')
    from word in words
    let w = word.ToLower()
    where w[0] == 'a' || w[0] == 'e'
        || w[0] == 'i' || w[0] == 'o'
        || w[0] == 'u'
    select word;

join

  join 子句可将来自不同源序列,并且在对象模型中没有直接关系的元素相关联。 关联的要求是:不同源数据用于关联的元素可以进行比较以判断是否相等。比如:食品经销商拥有的a-某种产品的供应商列表,以及b-买主列表,那么,可以使用 join 子句创建该产品(a)同一指定地区供应商和买主(b)的列表。
  在 LINQ 中,仅当两个源序列没有通过任何关系相互联系时,才需要使用显式 join 子句。(因为使用 LINQ to SQL 时,外键表在对象模型中表示为主表的属性。 例如,在 Northwind 数据库中,Customer 表与 Orders 表之间具有外键关系。 将这两个表映射到对象模型时,Customer 类具有一个 Orders 属性,其中包含与该 Customer 相关联的 Orders 集合。 实际上,已经为你执行了联接。)
  在连接时,只能使用相等的判断来连接,不能使用“>”或“<”。因此这里避免歧义或错误,只能使用equals关键字。
  join分三种联接

  1. 内部联接
      内部联接,只返回第一个数据源中在第二个数据源中具有匹配项的对象。内部联接又有以下几种类型:
    1.1. 简单键联接
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

public static void InnerJoinExample()
{
    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" };

    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 = rui };
    Pet daisy = new Pet { Name = "Daisy", Owner = magnus };
    
    List<Person> people = new List<Person> { magnus, terry, charlotte, arlene, rui };
    List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

    //people在pets中有匹配的,即人和宠物能对应上的所有成对数据
    var query = from person in people
                join pet in pets on person equals pet.Owner
                select new { OwnerName = person.FirstName, PetName = pet.Name };

    foreach (var ownerAndPet in query)
    {
        Console.WriteLine($"\"{ownerAndPet.PetName}\" is owned by {ownerAndPet.OwnerName}");
    }
}

// "Daisy" is owned by Magnus
// "Barley" is owned by Terry
// "Boots" is owned by Terry
// "Whiskers" is owned by Charlotte
// "Blue Moon" is owned by Rui

  1.2. 复合键联接
  即基于多个属性来比较元素
例如:

IEnumerable<string> query = from employee in employees
                            join student in students on new { employee.FirstName, employee.LastName }
                            equals new { student.FirstName, studen  LastName }
                            select employee.FirstName + " "     employee.LastName;

  1.3. 多联接
  可以将任意数量的联接操作相互追加。
例如:

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 { CatName = cat.Name, DogName = dog.Name };
  1. 分组联接
      它将第一个集合中的每个元素与第二个集合中的一组相关元素进行配对。对比内部联接,主要的区别在于对结果的组织方式。
      类似内部联接中简单联接的例子,用分组联接来实现:
//数据源等相关代码省略了
var query = from person in people
            join pet in pets on person equals pet.Owner into gj
            select new { OwnerName = person.FirstName, Pets = gj };

foreach (var v in query)
{
    // Output the owner's name.
    Console.WriteLine($"{v.OwnerName}:");
    // Output each of the owner's pet's names.
    foreach (Pet pet in v.Pets)
        Console.WriteLine($"  {pet.Name}");
}
// Magnus:
//   Daisy
// Terry:
//   Barley
//   Boots
//   Blue Moon
// Charlotte:
//   Whiskers
// Arlene:
  1. 左外部联接
      返回第一个集合的每个元素,无论该元素在第二个集合中是否有任何相关元素。与内部联接相比,区别主要在于,内部联接是求交集,而左外部联接,即保证第一个(左)为全集。通常要使用DefaultIfEmpty 方法来执行左外部联接。
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?.Name ?? String.Empty };

foreach (var v in query)
{
    Console.WriteLine($"{v.FirstName+":",-15}{v.PetName}");
}

// Magnus:        Daisy
// Terry:         Barley
// Terry:         Boots
// Rui:           Blue Moon
// Charlotte:     Whiskers
// Arlene:

这三种联接,可以类比成一种嵌套循环

  • 内部联接是在嵌套循环中输出所有找到匹配
  • 分组联接是内部嵌套循环结束后,将结果整理归总到外层循环的元素上在输出
  • 左外不联接,即在分组联接的基础上,在外层联接没有匹配到时给与默认空值以确保外层循环所有元素都能被输出。

on

和join一起使用,join on
可以参考英语中的用法加以理解
This piece of string is too short. Join another piece on to it. (这条绳子太短,再续上一截儿吧。)


参考:
语言集成查询 (LINQ)
查询关键字(C# 参考)

你可能感兴趣的:(假装会写C#,c#,linq,开发语言)