开放的编程资料库

当前位置:我爱分享网 > C#教程 > 正文

C# LINQ 查询表达式

C#LINQ查询表达式教程展示了如何在C#中使用查询提取和转换数据。

语言集成查询(LINQ)是一组基于将查询功能直接集成到C#语言中的技术。

查询是一组指令,描述了要从给定数据源(或多个数据源)检索哪些数据,以及返回的数据应具有何种形状和组织。LINQ查询表达式可以方便地从数组、可枚举类、XML文档、关系数据库和第三方数据源中提取和处理数据。

查询表达式可用于查询和转换来自任何支持LINQ的数据源的数据。查询表达式已延迟执行。在我们遍历查询变量之前,它们不会被执行,例如,在foreach语句中。

C#查询表达式过滤器

在接下来的示例中,我们将展示如何过滤数据。

int[] vals = { -2, 4, 6, -1, 2, 0, 1, -3, -4, 2, 3, 8 };

var pos = 
    from val in vals 
    where val > 0
    select val;

Console.WriteLine(string.Join(" ", pos));

var evens = 
    from val in vals
    where val % 2 == 0
    select val;

Console.WriteLine(string.Join(" ", evens));

在示例中,我们从数组中过滤掉正值和偶数。

var pos = 
    from val in vals 
    where val > 0
    select val;

使用from子句,我们遍历数组的元素。元素使用where进行过滤,最后使用select进行投影。

$ dotnet run 
4 6 2 1 2 3 8
-2 4 6 2 0 -4 2 8

接下来我们将条件与||运算符结合起来。

var words = new List<string> { "sky", "bee", "forest", "new", "falcon", "rock",
        "cloud", "war", "small", "eagle", "blue", "frost", "water" };

var found = 
    from word in words
    where word.StartsWith("f") || word.StartsWith("w")
    select word;

foreach (var word in found)
{
    Console.WriteLine(word);
}

我们在where子句中应用了两个条件。我们抓取以“f”或“w”开头的单词。

$ dotnet run
forest
falcon
war
frost
water

在下面的示例中,我们使用&&应用两个条件。

var cars = new List<Car>
{
    new ("Audi", 52642),
    new ("Mercedes", 57127),
    new ("Skoda", 9000),
    new ("Volvo", 29000),
    new ("Bentley", 350000),
    new ("Citroen", 21000),
    new ("Hummer", 41400),
    new ("Volkswagen", 21600)
};

var res = from car in cars
          where car.Price > 30000 && car.Price < 100000
          select new { car.Name, car.Price };

foreach (var car in res)
{
    Console.WriteLine($"{car.Name} {car.Price}");
}

record Car(string Name, int Price);

在示例中,我们使用where子句过滤汽车对象列表。我们包括所有价格在30000到100000之间的汽车。

$ dotnet run
Audi 52642
Mercedes 57127
Hummer 41400

C#查询第一个/最后一个

我们使用FirstLast方法获取可枚举的第一个和最后一个元素。

string[] words = { "falcon", "oak", "sky", "cloud", "tree", "tea", "water" };

var first = (from word in words
        where word.Length == 3
        select word).First();

Console.WriteLine(first);

var last = (from word in words
        where word.Length == 3
        select word).Last();

Console.WriteLine(last);

在示例中,我们访问数组的元素。

var first = (from word in words
    where word.Length == 3
    select word).First();

我们得到第一个长度为3的元素。

var last = (from word in words
    where word.Length == 3
    select word).Last();

我们检索最后一个长度为3的元素。

$ dotnet run
oak
tea

C#查询选择

select子句将序列中的每个元素投射到新形式中。它选择、投影和转换集合中的元素。select在其他语言中通常称为Map。

int[] vals = { 2, 4, 6, 8 };

var powered = 
    from val in vals 
    select Math.Pow(val, 2);

Console.WriteLine(string.Join(", ", powered));

string[] words = { "sky", "earth", "oak", "falcon" };
var wordLens = 
    from word in words
    select word.Length;

Console.WriteLine(string.Join(", ", wordLens));

在示例中,我们将一个整数数组转换为它的幂序列,并将一个单词数组转换为单词长度序列。

$ dotnet run
4, 16, 36, 64
3, 5, 3, 6

C#查询选择成匿名类型

投影是从返回的对象中选择特定字段。投影是使用select子句执行的。我们可以将字段投影到匿名类型中。

User[] users =
{
  new (1, "John", "London", "2001-04-01"),
  new (2, "Lenny", "New York", "1997-12-11"),
  new (3, "Andrew", "Boston", "1987-02-22"),
  new (4, "Peter", "Prague", "1936-03-24"),
  new (5, "Anna", "Bratislava", "1973-11-18"),
  new (6, "Albert", "Bratislava", "1940-12-11"),
  new (7, "Adam", "Trnava", "1983-12-01"),
  new (8, "Robert", "Bratislava", "1935-05-15"),
  new (9, "Robert", "Prague", "1998-03-14"),
};

var res = from user in users
          where user.City == "Bratislava"
          select new { user.Name, user.City };

Console.WriteLine(string.Join(", ", res));

record User(int id, string Name, string City, string DateOfBirth);

在示例中,我们选择居住在布拉迪斯拉发的用户。

var res = from user in users
          where user.City == "Bratislava"
          select new { user.Name, user.City };

通过selectnew子句,我们创建了一个具有两个字段的匿名类型:NameCity

$ dotnet run
{ Name = Anna, City = Bratislava }, { Name = Albert, City = Bratislava },
{ Name = Robert, City = Bratislava }

C#查询展平数组

以下示例将数组的数组展平为单个数组。

int[][] vals = {
    new[] {1, 2, 3},
    new[] {4},
    new[] {5, 6, 6, 2, 7, 8},
};

var res = (from nested in vals
           from e in nested
           select e);

Console.WriteLine(string.Join(", ", res));

为了展平数组,我们使用了两个from子句。

$ dotnet run
1, 2, 2, 3, 4, 5, 6, 6, 7, 8

C#查询Concat

Concat方法连接两个序列。

User[] users1 = 
{
    new ("John", "Doe", "gardener"),
    new ("Jane", "Doe", "teacher"), 
    new ("Roger", "Roe", "driver"),
};

User[] users2 = 
{
    new ("Peter", "Smith", "teacher"),
    new ("Lucia", "Black", "accountant"), 
    new ("Michael", "Novak", "programmer"),
};

var users = (from u1 in users1 select u1).Concat(from u2 in users2 select u2);

foreach (var user in users)
{
    Console.WriteLine(user);
}

record User(string FirstName, string LastName, string Occupation);

我们有两个用户数组。我们将它们与Concat合并。

$ dotnet run
User { FirstName = John, LastName = Doe, Occupation = gardener }
User { FirstName = Jane, LastName = Doe, Occupation = teacher }
User { FirstName = Roger, LastName = Roe, Occupation = driver }
User { FirstName = Peter, LastName = Smith, Occupation = teacher }
User { FirstName = Lucia, LastName = Black, Occupation = accountant }
User { FirstName = Michael, LastName = Novak, Occupation = programmer }

C#查询笛卡尔积

笛卡尔积是两个集合的乘积,以形成所有有序对的集合。

char[] letters = "abcdefghi".ToCharArray();
char[] digits = "123456789".ToCharArray();
 
var coords =
    from l in letters
    from d in digits
    select $"{l}{d}";
 
foreach (var coord in coords)
{
    Console.Write($"{coord} ");

    if (coord.EndsWith("9"))
    {
        Console.WriteLine();
    }
}

Console.WriteLine();

在示例中,我们创建了字母和数字的笛卡尔积。

var coords =
    from l in letters
    from d in digits
    select $"{l}{d}";

为了完成这个任务,我们使用了两个from子句。

$ dotnet run
a1 a2 a3 a4 a5 a6 a7 a8 a9 
b1 b2 b3 b4 b5 b6 b7 b8 b9 
c1 c2 c3 c4 c5 c6 c7 c8 c9 
d1 d2 d3 d4 d5 d6 d7 d8 d9 
e1 e2 e3 e4 e5 e6 e7 e8 e9 
f1 f2 f3 f4 f5 f6 f7 f8 f9 
g1 g2 g3 g4 g5 g6 g7 g8 g9 
h1 h2 h3 h4 h5 h6 h7 h8 h9 
i1 i2 i3 i4 i5 i6 i7 i8 i9 

C#查询计数和求和

Count返回序列中元素的数量。Sum计算一系列数值的总和。

var content =
    @"Foxes are omnivorous mammals belonging to several genera
of the family Canidae. Foxes have a flattened skull, upright triangular ears,
a pointed, slightly upturned snout, and a long bushy tail. Foxes live on every
continent except Antarctica. By far the most common and widespread species of
fox is the red fox.";

var lines = content.Split("\n");

var n1 = (from line in lines
          select line).Count();

Console.WriteLine($"There are {n1} lines");

var words = content.Split(" ");

var n2 = (from word in words
          select word).Count();

Console.WriteLine($"There are {n2} words");

var chars = content.ToCharArray();

var n3 = (from c in chars
          where c == 'f'
          select c).Count();

Console.WriteLine($"There are {n3} f letters");

在示例中,我们使用Count方法来计算文本中的行数、单词数和’f’字符数。

$ dotnet run
There are 5 lines
There are 47 words
There are 7 f letters

在下一个示例中,我们使用Sum方法。

var vals = new List<int> { 1, -2, 3, -4, 5, 6, 7, -8 };
var s = (from x in vals where x > 0 select x).Sum();

Console.WriteLine($"The sum of positive values is: {s}");

var words = new List<string> { "falcon", "eagle", "hawk", "owl" };
int len = (from x in words select x.Length).Sum();

Console.WriteLine($"There are {len} letters in the list");

在示例中,我们计算了vals列表中正值的数量和words列表中的字符数。

$ dotnet run
The sum of positive values is: 22
There are 18 letters in the list

C#查询顺序

使用OrderBy方法或orderby子句,我们可以对序列的元素进行排序。

int[] vals = { 4, 5, 3, 2, 7, 0, 1, 6 };

var result = from e in vals
             orderby e ascending
             select e;

Console.WriteLine(string.Join(", ", result));

var result2 = from e in vals
              orderby e descending
              select e;

Console.WriteLine(string.Join(", ", result2));

在示例中,我们按升序和降序对整数进行排序。ascending关键字是可选的。

$ dotnet run
0, 1, 2, 3, 4, 5, 6, 7
7, 6, 5, 4, 3, 2, 1, 0

在下一个示例中,我们按多个字段对对象进行排序。

var users = new List<User>
{
    new ("Robert", "Novak", 1770),
    new ("John", "Doe", 1230),
    new ("Lucy", "Novak", 670),
    new ("Ben", "Walter", 2050),
    new ("Robin", "Brown", 2300),
    new ("Amy", "Doe", 1250),
    new ("Joe", "Draker", 1190),
    new ("Janet", "Doe", 980),
    new ("Peter", "Novak", 990),
    new ("Albert", "Novak", 1930),
};

Console.WriteLine("sort descending by last name and salary");

var sortedUsers = from user in users
                  orderby user.LastName descending, user.Salary descending
                  select user;

foreach (var user in sortedUsers)
{
    Console.WriteLine(user);
}

Console.WriteLine("-------------------------");

Console.WriteLine("sort ascending by last name and salary");

var sortedUsers2 = from user in users
                   orderby user.LastName ascending, user.Salary ascending
                   select user;

foreach (var user in sortedUsers2)
{
    Console.WriteLine(user);
}

record User(string FirstName, string LastName, int Salary);

在示例中,首先按姓氏对用户进行排序,然后按工资排序。

var sortedUsers = from user in users
    orderby user.LastName descending, user.Salary descending
    select user;

在这里,我们先按姓氏对用户进行排序,然后按工资降序排列。

var sortedUsers2 = from user in users
    orderby user.LastName ascending, user.Salary ascending
    select user;

在这里,我们先按姓氏对用户进行排序,然后按工资升序对用户进行排序。

$ dotnet run
sort descending by last name and salary
User { FirstName = Ben, LastName = Walter, Salary = 2050 }
User { FirstName = Albert, LastName = Novak, Salary = 1930 }
User { FirstName = Robert, LastName = Novak, Salary = 1770 }
User { FirstName = Peter, LastName = Novak, Salary = 990 }
User { FirstName = Lucy, LastName = Novak, Salary = 670 }
User { FirstName = Joe, LastName = Draker, Salary = 1190 }
User { FirstName = Amy, LastName = Doe, Salary = 1250 }
User { FirstName = John, LastName = Doe, Salary = 1230 }
User { FirstName = Janet, LastName = Doe, Salary = 980 }
User { FirstName = Robin, LastName = Brown, Salary = 2300 }
-------------------------
sort ascending by last name and salary
User { FirstName = Robin, LastName = Brown, Salary = 2300 }
User { FirstName = Janet, LastName = Doe, Salary = 980 }
User { FirstName = John, LastName = Doe, Salary = 1230 }
User { FirstName = Amy, LastName = Doe, Salary = 1250 }
User { FirstName = Joe, LastName = Draker, Salary = 1190 }
User { FirstName = Lucy, LastName = Novak, Salary = 670 }
User { FirstName = Peter, LastName = Novak, Salary = 990 }
User { FirstName = Robert, LastName = Novak, Salary = 1770 }
User { FirstName = Albert, LastName = Novak, Salary = 1930 }
User { FirstName = Ben, LastName = Walter, Salary = 2050 }

C#反向查询

Reverse方法反转序列中元素的顺序。(请注意,这与按降序排序不同。)

int[] vals = { 1, 3, 6, 0, -1, 2, 9, 9, 8 };

var reversed = (from val in vals select val).Reverse();
Console.WriteLine(string.Join(", ", reversed));

在示例中,我们使用方法和查询语法反转数组的元素。

$ dotnet run
8, 9, 9, 2, -1, 0, 6, 3, 1

C#LINQ分组方式

我们可以根据某个键将数据分组。

var cars = new List<Car>
{
    new ("Audi", "red", 52642),
    new ("Mercedes", "blue", 57127),
    new ("Skoda", "black", 9000),
    new ("Volvo", "red", 29000),
    new ("Bentley", "yellow", 350000),
    new ("Citroen", "white", 21000),
    new ("Hummer", "black", 41400),
    new ("Volkswagen", "white", 21600),
};

var groups = from car in cars
             group car by car.Colour;

foreach (var group in groups)
{
    Console.WriteLine(group.Key);

    foreach (var car in group)
    {
        Console.WriteLine($" {car.Name} {car.Price}");
    }
}

record Car(string Name, string Colour, int Price);

在示例中,我们将可用汽车按颜色分组。

$ dotnet run
red
  Audi 52642
  Volvo 29000
blue
  Mercedes 57127
black
  Skoda 9000
  Hummer 41400
yellow
  Bentley 350000
white
  Citroen 21000
  Volkswagen 21600

在下面的示例中,我们执行分组和聚合操作。

Revenue[] revenues = 
{
    new (1, "Q1", 2340),
    new (2, "Q1", 1200),
    new (3, "Q1", 980),
    new (4, "Q2", 340),
    new (5, "Q2", 780),
    new (6, "Q3", 2010),
    new (7, "Q3", 3370),
    new (8, "Q4", 540),
};

var res = from revenue in revenues
          group revenue by revenue.Quarter
          into g
          select new { Quarter = g.Key, Total = g.Sum(e => e.Amount) };

foreach (var line in res)
{
    Console.WriteLine(line);
}

record Revenue(int Id, string Quarter, int Amount);

我们有四个季度的收入。我们按季度对收入进行分组并对金额求和。

$ dotnet run
{ Quarter = Q1, Total = 4520 }
{ Quarter = Q2, Total = 1120 }
{ Quarter = Q3, Total = 5380 }
{ Quarter = Q4, Total = 540 }

我们可以使用where子句对聚合数据应用过滤器。

Revenue[] revenues = 
{
    new (1, "Q1", 2340),
    new (2, "Q1", 1200),
    new (3, "Q1", 980),
    new (4, "Q2", 340),
    new (5, "Q2", 780),
    new (6, "Q3", 2010),
    new (7, "Q3", 3370),
    new (8, "Q4", 540),
};

var res = from revenue in revenues
          group revenue by revenue.Quarter
          into g
          where g.Count() == 2
          select new { Quarter = g.Key, Total = g.Sum(c => c.Amount) };


foreach (var line in res)
{
    Console.WriteLine(line);
}

record Revenue(int Id, string Quarter, int Amount);

在示例中,我们仅选择恰好有两个收入的那些季度。

$ dotnet run
{ Quarter = Q2, Total = 1120 }
{ Quarter = Q3, Total = 5380 }

C#LINQ词频

在下一个示例中,我们计算文件中单词的频率。

$ wget https://raw.githubusercontent.com/janbodnar/data/main/the-king-james-bible.txt

我们使用钦定版圣经。

using System.Text.RegularExpressions;

var fileName = "the-king-james-bible.txt";
var text = File.ReadAllText(fileName);

var dig = new Regex(@"\d");
var matches = new Regex("[a-z-A-Z']+").Matches(text);

var words = 
    from match in matches
        let val = match.Value
        where !dig.IsMatch(val)
    select match.Value;

var topTen =
    (from word in words
        group word by word into wg
        orderby wg.Count() descending
        select new {word = wg.Key, Total = wg.Count()}
    ).Take(10);

foreach (var e in topTen)
{
    Console.WriteLine($"{e.word}: {e.Total}");
}

我们计算KingJames圣经中单词的出现频率。

var matches = new Regex("[a-z-A-Z']+").Matches(text);
var words = matches.Select(m => m.Value).ToList();

我们通过Matches方法找到所有匹配项。从matchcollection中,我们将所有单词放入一个列表中。

var words = 
from match in matches
    let val = match.Value
    where !dig.IsMatch(val)
select match.Value;

在第一个查询中,我们找到了所有匹配项。从匹配集合中,我们得到了所有的单词。

var topTen =
    (from word in words
        group word by word into wg
        orderby wg.Count() descending
        select new {word = wg.Key, Total = wg.Count()}
    ).Take(10);

单词按频率降序分组和排序。我们取前十个最常用的词。

$ dotnet run
the 62103
and 38848
of 34478
to 13400
And 12846
that 12576
in 12331
shall 9760
he 9665
unto 8942

C#LINQ连接

join子句连接序列。

string[] basketA = { "coin", "book", "fork", "cord", "needle" };
string[] basketB = { "watches", "coin", "pen", "book", "pencil" };

var res = from item1 in basketA
          join item2 in basketB
          on item1 equals item2
          select item1;

foreach (var item in res)
{
    Console.WriteLine(item);
}

示例中有两个数组。使用join子句,我们找到两个数组中都存在的所有项目。

$ dotnet run
coin
book

两个数组中都包含硬币和书本单词。

C#查询转换

我们可以将返回的枚举转换为列表、数组或字典。

User[] users =
{
  new (1, "John", "London", "2001-04-01"),
  new (2, "Lenny", "New York", "1997-12-11"),
  new (3, "Andrew", "Boston", "1987-02-22"),
  new (4, "Peter", "Prague", "1936-03-24"),
  new (5, "Anna", "Bratislava", "1973-11-18"),
  new (6, "Albert", "Bratislava", "1940-12-11"),
  new (7, "Adam", "Trnava", "1983-12-01"),
  new (8, "Robert", "Bratislava", "1935-05-15"),
  new (9, "Robert", "Prague", "1998-03-14"),
};

string[] cities = (from user in users
                   select user.City).Distinct().ToArray();

Console.WriteLine(string.Join(", ", cities));

Console.WriteLine("------------");

List<User> inBratislava = (from user in users
                                 where user.City == "Bratislava"
                                 select user).ToList();

foreach (var user in inBratislava)
{
    Console.WriteLine(user);
}

Console.WriteLine("------------");

Dictionary<int, string> userIds =
        (from user in users
         select user).ToDictionary(user => user.id, user => user.Name);

foreach (var kvp in userIds)
{
    Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}

record User(int id, string Name, string City, string DateOfBirth);

我们对数据源执行三个查询;生成的枚举被转换为列表、数组和字典。

string[] cities = (from user in users
                   select user.City).Distinct().ToArray();

在此查询中,我们从数据源中选择所有城市。我们应用Distinct方法,最后调用ToArray方法。

List<User> inBratislava = (from user in users
                                 where user.City == "Bratislava"
                                 select user).ToList();

这里我们得到了居住在布拉迪斯拉发的用户列表;我们调用ToList方法。

Dictionary<int, string> userIds =
        (from user in users
         select user).ToDictionary(user => user.id, user => user.Name);

在此查询中,我们将用户名及其ID转换为字典。

$ dotnet run
London, New York, Boston, Prague, Bratislava, Trnava
------------
User { id = 5, Name = Anna, City = Bratislava, DateOfBirth = 1973-11-18 }
User { id = 6, Name = Albert, City = Bratislava, DateOfBirth = 1940-12-11 }
User { id = 8, Name = Robert, City = Bratislava, DateOfBirth = 1935-05-15 }
------------
1: John
2: Lenny
3: Andrew
4: Peter
5: Anna
6: Albert
7: Adam
8: Robert
9: Robert

C#查询XML

LINQ可用于处理XML。

using System.Xml.Linq;

string myXML = @"
<Users>
    <User>
        <Name>Jack Moore</Name>
        <Occupation>programmer</Occupation>
    </User>
    <User>
        <Name>Paul Novak</Name>
        <Occupation>driver</Occupation>
    </User>
    <User>
        <Name>Frank Woody</Name>
        <Occupation>teacher</Occupation>
    </User>
    <User>
        <Name>Martina Doe</Name>
        <Occupation>programmer</Occupation>
    </User>
    <User>
        <Name>Lucia Black</Name>
        <Occupation>teacher</Occupation>
    </User>
</Users>";

var xdoc = new XDocument();
xdoc = XDocument.Parse(myXML);

var data = from u in xdoc.Root.Descendants()
              where (string)u.Element("Occupation") == "teacher"
              select u.Element("Name");

foreach (var e in data)
{
    Console.WriteLine($"{e}");
}

我们解析XML数据并选择所有女性名字。

$ dotnet run
<Name>Frank Woody</Name>
<Name>Lucia Black</Name>

C#查询列表目录内容

Directory.EnumerateFiles返回满足指定条件的完整文件名的可枚举集合。

var path = "/home/user2/";

var files = from file in Directory.EnumerateFiles(path, "*.txt",
                SearchOption.AllDirectories)
            where Path.GetFileName(file).ToLower().Contains("data")
            select file;

foreach (var file in files)
{
    Console.WriteLine("{0}", file);
}

Console.WriteLine("{0} files found.", files.Count<string>().ToString());

该示例以递归方式搜索名称中包含单词data的所有文本文件。

C#查询let子句

let子句允许我们存储子表达式的结果,以便在后续子句中使用它。

John Doe, gardener, 12/5/1997
Jane Doe, teacher, 5/16/1983
Robert Smith, driver, 4/2/2001
Maria Smith, cook, 9/21/1976

这些是data.csv文件的内容。

using System.Text;

var path = "data.csv";
var lines = File.ReadLines(path, Encoding.UTF8);

var users = from line in lines
            let fields = line.Replace(", ", ",").Split(",")
            select new User(fields[0], fields[1], DateTime.Parse(fields[2]));

var sorted = from user in users
             orderby user.DateOfBirth descending
             select user;

foreach (var user in sorted)
{
    Console.WriteLine(user);
}

public record User(string Name, string Occupation, DateTime DateOfBirth);

在示例中,我们解析data.csv文件并创建一个用户序列;用户按出生日期降序排列。

var users = from line in lines
    let fields = line.Replace(", ", ",").Split(",")
    select new User(fields[0], fields[1], DateTime.Parse(fields[2]));

在第一个查询表达式中,我们将一行拆分为其字段;字段存储在fields变量中,该变量稍后在select子句中使用。

$ dotnet run
User { Name = Robert Smith, Occupation = driver, DateOfBirth = 4/2/2001 12:00:00 AM }
User { Name = John Doe, Occupation = gardener, DateOfBirth = 12/5/1997 12:00:00 AM }
User { Name = Jane Doe, Occupation = teacher, DateOfBirth = 5/16/1983 12:00:00 AM }
User { Name = Maria Smith, Occupation = cook, DateOfBirth = 9/21/1976 12:00:00 AM }

在本文中,我们使用了C#中的LINQ查询表达式。

列出所有C#教程。

未经允许不得转载:我爱分享网 » C# LINQ 查询表达式

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏