• LINQ之路11:LINQ Operators之过滤(Filtering)


    在本系列博客前面的篇章中,已经对LINQ的作用、C# 3.0为LINQ提供的新特性,还有几种典型的LINQ技术:LINQ to Objects、LINQ to SQL、Entity Framework进行了比较详细的介绍,至此,我们应该了解了各种LINQ技术之间的联系和区别。千里之行始于足下,这些基础理论是理解和使用LINQ的关键。但是我们在前面的文章中对于LINQ查询运算符(LINQ Operators)并没有完整的介绍,这就是接下来这几篇博客中所要做的工作。大家可以按顺序依次对各个LINQ Operators进行学习,也可以把他们看成一个reference,作为参考查询之用。

    示例数据

    在这几篇讨论LINQ Operators的文章中,所有的示例都会(如果需要)用到下面的names数组:

                string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };

    所有查询数据库的示例都会假设我们创建了强类型的DataCotnext变量dataContext,如下:

            var dataContext = new LifePoemContext ("connection string...");
    ...
    public class LifePoemContext : DataContext
    {
    public LifePoemContext (string cxString) : base (cxString) {}
    public Table<Customer> Customers { get { return GetTable<Customer>(); } }
    public Table<Purchase> Purchases { get { return GetTable<Purchase>(); } }
    }

    [Table] public class Customer
    {
    [Column(IsPrimaryKey=true)] public int ID;
    [Column] public string Name;

    [Association (OtherKey="CustomerID")]
    public EntitySet<Purchase> Purchases = new EntitySet<Purchase>();
    }

    [Table] public class Purchase
    {
    [Column(IsPrimaryKey=true)] public int ID;
    [Column] public int? CustomerID;
    [Column] public string Description;
    [Column] public decimal Price;

    EntityRef<Customer> custRef;
    [Association (Storage="custRef",ThisKey="CustomerID",IsForeignKey=true)]
    public Customer Customer
    {
    get { return custRef.Entity; } set { custRef.Entity = value; }
    }
    }

    上面的DataContext和实体类是LINQ to SQL工具(如通过Visual Studio新增一个”LINQ to SQL Classes” Item)生成的简化版本。其中Customer和Purchase包含了一个简单的1:多关系,下面是对应的SQL表定义:

            create table Customer
    (
    ID int not null primary key,
    Name varchar(30) not null
    )
    create table Purchase
    (
    ID int not null primary key,
    CustomerID int references Customer (ID),
    Description varchar(30) not null,
    Price decimal not null
    )

    Filtering Operators

    IEnumerable<TSource> → IEnumerable<TSource>

    返回输入sequence中元素的一个子集

    Operator

    说明

    SQL语义

    Where

    返回符合给定条件的elements子集

    WHERE

    Take

    返回开始的N个元素,忽略剩下的元素

    WHERE   ROW_NUMBER()...

    或TOP n 子句

    Skip

    忽略开始的N歌元素,返回之后的元素

    WHERE ROW_NUMBER()...

    或NOT IN (SELECT TOP n...)

    TakeWhile

    返回输入sequence中的元素,直到指定条件为false,然后忽略剩下的元素

    Exception   thrown

    SkipWhile

    忽略输入sequence中的元素,直到指定条件为false,然后返回剩下的元素

    Exception thrown

    Distinct

    返回去除重复元素的sequence

    SELECT   DISTINCT...

    对每一个过滤方法而言,总是得到一个少于或等于初始元素个数的序列,而不可能返回更多的元素!并且这些元素不会经过任何改变,而是与原始元素一致。

    Where

    参数

    类型

    Source sequence

    IEnumerable<TSource>

    Predicate/条件

    TSource   => bool or (TSource,int) => boola

    a带索引的lambda表达式在LINQ to SQL和Entity Framework中不可用。

    查询表达式语法:where bool-expression

    Enumerable.Where实现

    Enumerable.Where的内部实现大致如下(略去null引用的检查):

            public static IEnumerable<TSource> Where<TSource>
    (this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
    foreach (TSource element in source)
    if (predicate(element))
    yield return element;
    }

    介绍

    Where返回输入sequence中满足指定条件的elements,比如:

                string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
    IEnumerable<string> query = names.Where(name => name.EndsWith("y"));
    // Result: { "Harry", "Mary", "Jay" }

    对应的查询表达式语法:

                IEnumerable<string> query = from n in names
    where n.EndsWith("y")
    select n;

    在一个查询中where子句可以出现多次并且可以和let子句搭配使用:

                string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
    IEnumerable<string> query = from n in names
    where n.Length > 3
    let u = n.ToUpper()
    where u.EndsWith("Y")
    select u;
    // Result: { "HARRY", "MARY" }

    索引过滤

    Where的条件支持第二个可选参数,类型为int,它的值等于每个element在输入sequence中的位置,这样我们就可以在lambda表达式中使用这个index信息。比如,下面的例子忽略index为单数的elements:

                IEnumerable<string> query = names.Where((n, i) => i % 2 == 0);
    // Result: { "Tom", "Harry", "Jay" }

    如果你在LINQ to SQL或Entity Framework中使用索引参数,将会抛出异常。

    LINQ to SQL和EF中的SQL LIKE 比较

    下面这些字符串方法会被翻译成SQL的LIKE操作符:Contains、StartsWith、EndsWith。

    比如:c.Name.Contains ("abc") 翻译成customer.Name LIKE '%abc%' (或者与之等价的参数版本)。

    需要注意的是,在使用Contains时,我们只能用来与本地计算的表达式进行比较,如果要和另外一列进行比较,我们必须借助于SqlMethod.Like方法:

                IQueryable<Purchase> query = purchases
    .Where(p => SqlMethods.Like(p.Description, "%" + p.Customer.Name + "%"))
    .OrderBy(p => p.ID)
    .Select(p => p);

    LINQ to SQL和EF中的WHERE x IN (..., ..., ...)

    对于LINQ to SQL和EF,我们可以在过滤条件中对一个本地集合应用Contains方法,比如:

                string[] chosenOnes = { "Tom", "Jay" };

    IQueryable<Customer> query =
    from c in dataContext.Customers
    where chosenOnes.Contains(c.Name)
    select c;

    这会映射到SQL的IN操作符,即:WHERE customer.Name IN ("Tom", "Jay")。如果本地集合是一个entities数组或其他的非标量类型,LINQ to SQL和EF可能会生成EXISTS子句。

    Take和Skips

    参数

    类型

    Source sequence

    IEnumerable<TSource>

    获取或忽略的elements个数

    int

    Take返回前面n个元素并丢弃剩下的元素;Skip丢弃前面n个元素并返回剩下的元素。这两个方法通常一起使用以实现web页面的数据分页效果,让用户能在一个大型的结果集上进行导航,比如:

                // 假设用户在一个图书数据库中查找包含"mercury"的所有图书
    // 如果查找结果包含100条记录,下面的查询会返回前面20条
    IQueryable<Book> query = dataContext.Books
    .Where (b => b.Title.Contains ("mercury"))
    .OrderBy (b => b.Title)
    .Take (20);

    // 下面的查询则返回第21到40行数据(第2页)
    IQueryable<Book> query = dataContext.Books
    .Where (b => b.Title.Contains ("mercury"))
    .OrderBy (b => b.Title)
    .Skip (20).Take (20);

    对于SQL Server 2005,LINQ to SQL和EF 会把Take和Skip翻译成ROW_NUMBER函数,而对于更早的SQL Server版本,它们会被翻译成Top n子句。

    TakeWhile和SkipWhile

    参数

    类型

    Source sequence

    IEnumerable<TSource>

    Predicate

    TSource   => bool or (TSource,int) => bool

    TakeWhile遍历输入sequence,返回每个element,直到给定的测试条件为false,然后忽略剩下的elements:

                int[] numbers = { 3, 5, 2, 234, 4, 1 };
    var takeWhileSmall = numbers.TakeWhile(n => n < 100); // { 3, 5, 2 }

    SkipWhile遍历输入sequence,忽略每个element,直到给定的测试条件为false,然后返回剩下的elements:

                int[] numbers = { 3, 5, 2, 234, 4, 1 };
    var skipWhileSmall = numbers.SkipWhile(n => n < 100); // { 234, 4, 1 }

    TakeWhile和SkipWhile没有对应的SQL翻译,如果在LINQ-to-db查询中使用他们会导致运行时错误。

    Distinct

    Distinct返回去除了重复元素之后的输入sequence,在确定元素是否重复时只能使用默认的相等比较方法:

                // The following returns distinct letters in a string
    char[] distinctLetters = "HelloWorld".Distinct().ToArray();
    string s = new string (distinctLetters); // HeloWrd

    我们可以在string变量上直接调用LINQ方法,因为string实现了IEnumerable<char>。

    在下一篇博客中,会详细讨论LINQ运算符中的数据转换:Select和SelectMany。


     
  • 相关阅读:
    HDOJ 1712 ACboy needs your help(分组背包入门)
    POJ 1742 Coins(多重背包 + 单调队列优化)
    Windows 底层驱动级 AntiRootkit 工具 ScDetective 源代码
    POJ 1252 Euro Efficiency(完全背包变型)
    POJ 2392 Space Elevator(多重背包 + 倍增优化)
    POJ 1384 PiggyBank(完全背包)
    POJ 2063 Investment(完全背包)
    POJ 1014 Dividing(多重背包 + 倍增优化)
    POJ 3260 The Fewest Coins(完全背包 + 多重背包 + 单调队列优化)
    POJ 3264 Balanced Lineup(线段树 单点更新 区间查询)
  • 原文地址:https://www.cnblogs.com/qixuejia/p/7284692.html
Copyright © 2020-2023  润新知